Skip to content

Commit 78d78d0

Browse files
committed
Added obj.py
1 parent 42cf448 commit 78d78d0

File tree

2 files changed

+219
-51
lines changed

2 files changed

+219
-51
lines changed

additional_file_formats/obj.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""
2+
I/O for the Wavefront .obj file format, cf.
3+
<https://en.wikipedia.org/wiki/Wavefront_.obj_file>.
4+
"""
5+
import datetime
6+
7+
import numpy as np
8+
9+
# from ..__about__ import __version__
10+
# from .._exceptions import WriteError
11+
# from .._files import open_file
12+
# from .._helpers import register_format
13+
# from .._mesh import CellBlock, Mesh
14+
15+
import meshio
16+
17+
18+
def read(filename):
19+
with open_file(filename, "r") as f:
20+
mesh = read_buffer(f)
21+
return mesh
22+
23+
24+
def read_buffer(f):
25+
points = []
26+
vertex_normals = []
27+
texture_coords = []
28+
face_groups = []
29+
face_normals = []
30+
face_texture_coords = []
31+
face_group_ids = []
32+
face_group_id = -1
33+
while True:
34+
line = f.readline()
35+
36+
if not line:
37+
# EOF
38+
break
39+
40+
strip = line.strip()
41+
42+
if len(strip) == 0 or strip[0] == "#":
43+
continue
44+
45+
split = strip.split()
46+
47+
if split[0] == "v":
48+
points.append([float(item) for item in split[1:]])
49+
elif split[0] == "vn":
50+
vertex_normals.append([float(item) for item in split[1:]])
51+
elif split[0] == "vt":
52+
texture_coords.append([float(item) for item in split[1:]])
53+
elif split[0] == "s":
54+
# "s 1" or "s off" controls smooth shading
55+
pass
56+
elif split[0] == "f":
57+
# old: dat = [int(item.split("/")[0]) for item in split[1:]]
58+
# A face in obj has one of the following formats: 1, 1/2, 1//3, 1/2/3
59+
# We want to support all formats now amd store the texture and normal indices in other arrays
60+
face_indices = []
61+
face_texture_indices = []
62+
face_normal_indices = []
63+
64+
for item in split[1:]:
65+
indices = item.split("/")
66+
face_indices.append(int(indices[0]))
67+
if len(indices) > 1 and indices[1] != "":
68+
face_texture_indices.append(int(indices[1]))
69+
if len(indices) > 2:
70+
face_normal_indices.append(int(indices[2]))
71+
72+
if len(face_groups) == 0 or (
73+
len(face_groups[-1]) > 0 and len(face_groups[-1][-1]) != len(face_indices)
74+
):
75+
face_groups.append([])
76+
face_group_ids.append([])
77+
face_texture_coords.append([])
78+
face_normals.append([])
79+
face_groups[-1].append(face_indices)
80+
face_group_ids[-1].append(face_group_id)
81+
if face_texture_indices:
82+
face_texture_coords[-1].append(face_texture_indices)
83+
if face_normal_indices:
84+
face_normals[-1].append(face_normal_indices)
85+
elif split[0] == "g":
86+
# new group
87+
face_groups.append([])
88+
face_group_ids.append([])
89+
face_texture_coords.append([])
90+
face_normals.append([])
91+
face_group_id += 1
92+
else:
93+
# who knows
94+
pass
95+
96+
# There may be empty groups, too. <https://github.com/nschloe/meshio/issues/770>
97+
# Remove them.
98+
face_groups = [f for f in face_groups if len(f) > 0]
99+
face_group_ids = [g for g in face_group_ids if len(g) > 0]
100+
face_normals = [n for n in face_normals if len(n) > 0]
101+
face_texture_coords = [t for t in face_texture_coords if len(t) > 0]
102+
103+
# convert to numpy arrays and remove
104+
points = np.array(points)
105+
face_groups = [np.array(f) for f in face_groups]
106+
texture_coords = [np.array(t) for t in texture_coords]
107+
vertex_normals = [np.array(n) for n in vertex_normals]
108+
point_data = {}
109+
cell_data = {}
110+
field_data = {}
111+
112+
if face_texture_coords and len(texture_coords) == max([max(max(face)) for face in face_texture_coords]):
113+
field_data["obj:vt"] = texture_coords
114+
cell_data["obj:vt_face_idx"] = face_texture_coords
115+
elif len(texture_coords) == len(points):
116+
point_data["obj:vt"] = texture_coords
117+
118+
if face_normals and len(vertex_normals) == max([max(max(face)) for face in face_normals]):
119+
field_data["obj:vn"] = vertex_normals
120+
cell_data["obj:vn_face_idx"] = face_normals
121+
elif len(vertex_normals) == len(points):
122+
point_data["obj:vn"] = vertex_normals
123+
124+
cell_data["obj:group_ids"] = []
125+
cells = []
126+
for f, gid in zip(face_groups, face_group_ids):
127+
if f.shape[1] == 3:
128+
cells.append(CellBlock("triangle", f - 1))
129+
elif f.shape[1] == 4:
130+
cells.append(CellBlock("quad", f - 1))
131+
else:
132+
cells.append(CellBlock("polygon", f - 1))
133+
cell_data["obj:group_ids"].append(gid)
134+
135+
return Mesh(points, cells, point_data=point_data, cell_data=cell_data, field_data=field_data)
136+
137+
138+
def write(filename, mesh):
139+
for c in mesh.cells:
140+
if c.type not in ["triangle", "quad", "polygon"]:
141+
raise WriteError(
142+
"Wavefront .obj files can only contain triangle or quad cells."
143+
)
144+
145+
with open_file(filename, "w") as f:
146+
f.write(
147+
"# Created by meshio v{}, {}\n".format(
148+
__version__, datetime.datetime.now().isoformat()
149+
)
150+
)
151+
for p in mesh.points:
152+
f.write(f"v {p[0]} {p[1]} {p[2]}\n")
153+
154+
if "obj:vn" in mesh.point_data:
155+
dat = mesh.point_data["obj:vn"]
156+
fmt = "vn " + " ".join(["{}"] * dat.shape[1]) + "\n"
157+
for vn in dat:
158+
f.write(fmt.format(*vn))
159+
160+
if "obj:vt" in mesh.point_data:
161+
dat = mesh.point_data["obj:vt"]
162+
fmt = "vt " + " ".join(["{}"] * dat.shape[1]) + "\n"
163+
for vt in dat:
164+
f.write(fmt.format(*vt))
165+
166+
for cell_block in mesh.cells:
167+
fmt = "f " + " ".join(["{}"] * cell_block.data.shape[1]) + "\n"
168+
for c in cell_block.data:
169+
f.write(fmt.format(*(c + 1)))
170+
171+
172+
register_format("obj", [".obj"], read, {"obj": write})

bseq/importer.py

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,31 @@ def apply_transformation(meshio_mesh, obj, depsgraph):
8787
# multiply everything together (with custom transform matrix)
8888
obj.matrix_world = rigid_body_transformation @ eval_transform_matrix
8989

90+
# function to create a single custom Blender mesh attribute
91+
def create_or_retrieve_attribute(mesh, k, v):
92+
if k not in mesh.attributes:
93+
if len(v) == 0:
94+
return mesh.attributes.new(k, "FLOAT", "POINT")
95+
if len(v.shape) == 1:
96+
# one dimensional attribute
97+
return mesh.attributes.new(k, "FLOAT", "POINT")
98+
if len(v.shape) == 2:
99+
dim = v.shape[1]
100+
if dim > 3:
101+
show_message_box('higher than 3 dimensional attribue, ignored')
102+
return None
103+
if dim == 1:
104+
return mesh.attributes.new(k, "FLOAT", "POINT")
105+
if dim == 2:
106+
return mesh.attributes.new(k, "FLOAT2", "POINT")
107+
if dim == 3:
108+
return mesh.attributes.new(k, "FLOAT_VECTOR", "POINT")
109+
if len(v.shape) > 2:
110+
show_message_box('more than 2 dimensional tensor, ignored')
111+
return None
112+
else:
113+
return mesh.attributes[k]
114+
90115
def update_mesh(meshio_mesh, mesh):
91116
# extract information from the meshio mesh
92117
mesh_vertices = meshio_mesh.points
@@ -141,30 +166,18 @@ def update_mesh(meshio_mesh, mesh):
141166
mesh.update()
142167
mesh.validate()
143168

169+
if bpy.context.scene.BSEQ.use_imported_normals:
170+
if "obj:vn" in meshio_mesh.point_data:
171+
mesh.BSEQ.split_norm_att_name = "bseq_obj:vn"
172+
elif "normals" in meshio_mesh.point_data and len(meshio_mesh.point_data["normals"]) == len(mesh.vertices):
173+
mesh.BSEQ.split_norm_att_name = "bseq_normals"
174+
elif "obj:vn" in meshio_mesh.field_data and "obj:vn_face_idx" in meshio_mesh.cell_data:
175+
mesh.BSEQ.split_norm_att_name = "obj:vn"
176+
144177
# copy attributes
145178
for k, v in meshio_mesh.point_data.items():
146179
k = "bseq_" + k
147-
attribute = None
148-
if k not in mesh.attributes:
149-
if len(v.shape) == 1:
150-
# one dimensional attribute
151-
attribute = mesh.attributes.new(k, "FLOAT", "POINT")
152-
if len(v.shape) == 2:
153-
dim = v.shape[1]
154-
if dim > 3:
155-
show_message_box('higher than 3 dimensional attribue, ignored')
156-
continue
157-
if dim == 1:
158-
attribute = mesh.attributes.new(k, "FLOAT", "POINT")
159-
if dim == 2:
160-
attribute = mesh.attributes.new(k, "FLOAT2", "POINT")
161-
if dim == 3:
162-
attribute = mesh.attributes.new(k, "FLOAT_VECTOR", "POINT")
163-
if len(v.shape) > 2:
164-
show_message_box('more than 2 dimensional tensor, ignored')
165-
continue
166-
else:
167-
attribute = mesh.attributes[k]
180+
attribute = create_or_retrieve_attribute(mesh, k, v)
168181
name_string = None
169182
if attribute.data_type == "FLOAT":
170183
name_string = "value"
@@ -173,36 +186,20 @@ def update_mesh(meshio_mesh, mesh):
173186

174187
attribute.data.foreach_set(name_string, v.ravel())
175188

176-
# # set as split norm
177-
# if mesh.BSEQ.split_norm_att_name and mesh.BSEQ.split_norm_att_name == k:
178-
# mesh.use_auto_smooth = True
179-
# mesh.normals_split_custom_set_from_vertices(v)
180-
181-
# I want to set normals if the scene property use_imported_normals is true and the normals are either in point_data["obj:vn"] or field_data["obj:vn"]
182-
if bpy.context.scene.BSEQ.use_imported_normals:
183-
print("use_imported_normals")
184-
# print all the keys in point_data, field_data, cell_data
185-
print("point_data", meshio_mesh.point_data.keys())
186-
print("field_data", meshio_mesh.field_data.keys())
187-
print("cell_data", meshio_mesh.cell_data.keys())
188-
189-
mesh.use_auto_smooth = True
190-
191-
192-
if "obj:vn" in meshio_mesh.point_data and len(meshio_mesh.point_data["obj:vn"]) == len(mesh.vertices):
193-
print("obj:vn in point_data", len(mesh.loops))
194-
# vert_norms = [tuple(x) for x in meshio_mesh.point_data["obj:vn"]]
195-
196-
mesh.normals_split_custom_set_from_vertices(meshio_mesh.point_data["obj:vn"])
189+
# set as split normal per vertex
190+
if mesh.BSEQ.split_norm_att_name and mesh.BSEQ.split_norm_att_name == k:
191+
mesh.use_auto_smooth = True
192+
mesh.normals_split_custom_set_from_vertices(v)
197193

198-
for i in range(len(mesh.vertices)):
199-
print(mesh.vertices[i].normal)
200-
elif "obj:vn" in meshio_mesh.field_data and "obj:vn_face_idx" in meshio_mesh.cell_data:
201-
print("obj:vn in field_data")
202-
indices = meshio_mesh.cell_data["obj:vn_face_idx"][0]
203-
indices = [item for sublist in indices for item in sublist]
204-
vert_norms = [meshio_mesh.field_data["obj:vn"][i - 1] for i in indices]
205-
mesh.normals_split_custom_set(vert_norms)
194+
for k, v in meshio_mesh.field_data.items():
195+
if k not in mesh.attributes:
196+
attribute = create_or_retrieve_attribute(mesh, k, [])
197+
198+
# set split normal per loop per vertex
199+
if mesh.BSEQ.split_norm_att_name and mesh.BSEQ.split_norm_att_name == k:
200+
# Currently hard-coded for .obj files
201+
indices = [item for sublist in meshio_mesh.cell_data["obj:vn_face_idx"][0] for item in sublist]
202+
mesh.normals_split_custom_set([meshio_mesh.field_data["obj:vn"][i - 1] for i in indices])
206203

207204
# function to create a single meshio object
208205
def create_meshio_obj(filepath):
@@ -223,7 +220,6 @@ def create_meshio_obj(filepath):
223220
bpy.ops.object.select_all(action="DESELECT")
224221
bpy.context.view_layer.objects.active = object
225222

226-
227223
def create_obj(fileseq, root_path, transform_matrix=Matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])):
228224

229225
current_frame = bpy.context.scene.frame_current

0 commit comments

Comments
 (0)