if obj is None:
obj = context.active_object
- objs = [(obj, mathutils.Matrix())]
- check = objs
- while check:
- children = []
- for o, m in check:
- for c in o.children:
- if c.compound:
- children.append((c, m*c.matrix_local))
- objs += children
- check = children
-
- if not objs:
- raise Exception("Nothing to export")
- for o, m in objs:
- if o.type!="MESH":
- raise Exception("Can only export Mesh data")
-
- from .mesh import Mesh
+ from .mesh import create_mesh_from_object
from .util import Progress
if self.show_progress:
else:
progress = None
- mesh = None
- bmeshes = []
- winding_test = False
- for o, m in objs:
- if o.data.winding_test:
- winding_test = True
- if o.material_tex:
- self.material_tex = True
- bmesh = o.to_mesh(context.scene, True, "PREVIEW")
- bmeshes.append(bmesh)
- me = Mesh(bmesh)
- me.transform(m)
- if not mesh:
- mesh = me
- else:
- mesh.splice(me)
-
- if progress:
- progress.set_task("Smoothing", 0.05, 0.35)
- if mesh.smoothing=="NONE":
- mesh.flatten_faces()
- mesh.split_smooth(progress)
-
- if mesh.smoothing!="BLENDER":
- mesh.compute_normals()
-
- if mesh.vertex_groups:
- mesh.sort_vertex_groups(mesh.max_groups_per_vertex)
-
- # Create a mapping from vertex group indices to bone indices
- first_obj = objs[0][0]
- group_index_map = dict((i, i) for i in range(len(first_obj.vertex_groups)))
- if first_obj.parent and first_obj.parent.type=="ARMATURE":
- armature = first_obj.parent.data
- bone_indices = dict((armature.bones[i].name, i) for i in range(len(armature.bones)))
- for g in first_obj.vertex_groups:
- if g.name in bone_indices:
- group_index_map[g.index] = bone_indices[g.name]
-
- if self.material_tex and mesh.materials:
- mesh.generate_material_uv()
-
- texunits = []
- force_unit0 = False
- if mesh.uv_layers and (mesh.use_uv!="NONE" or self.material_tex):
- # Figure out which UV layers to export
- if mesh.use_uv=="ALL":
- texunits = range(len(mesh.uv_layers))
- elif self.material_tex:
- # The material UV layer is always the last one
- texunits = [len(mesh.uv_layers)-1]
- force_unit0 = True
- else:
- for i, u in enumerate(mesh.uv_layers):
- if u.unit==0:
- texunits = [i]
- break
- texunits = [(i, mesh.uv_layers[i]) for i in texunits]
- texunits = [u for u in texunits if not u[1].hidden]
-
- if mesh.tbn_vecs:
- # TBN coordinates must be generated before vertices are split by any other layer
- uv_names = [u.name for i, u in texunits]
- if mesh.tbn_uvtex in uv_names:
- tbn_index = uv_names.index(mesh.tbn_uvtex)
- unit = texunits[tbn_index]
- del texunits[tbn_index]
- texunits.insert(0, unit)
-
- for i, u in texunits:
- if progress:
- progress.set_task("Splitting UVs", 0.35+0.3*i/len(texunits), 0.35+0.3*(i+1)/len(texunits))
- mesh.split_uv(i, progress)
- if mesh.tbn_vecs and u.name==mesh.tbn_uvtex:
- mesh.compute_uv()
- mesh.compute_tbn(i)
-
- mesh.compute_uv()
+ mesh = create_mesh_from_object(context, obj, progress)
strips = []
loose = mesh.faces
out_file = open_output(out_file)
fmt = ["NORMAL3"]
- if texunits:
- for i, u in texunits:
- size = str(len(mesh.vertices[0].uvs[i]))
- if u.unit==0 or force_unit0:
+ if mesh.uv_layers:
+ for u in mesh.uv_layers:
+ size = str(len(u.uvs[0]))
+ if u.unit==0:
fmt.append("TEXCOORD"+size)
else:
fmt.append("TEXCOORD%s_%d"%(size, u.unit))
if v.normal!=normal:
out_file.write("normal3", *v.normal)
normal = v.normal
- for i, u in texunits:
+ for i, u in enumerate(mesh.uv_layers):
if v.uvs[i]!=uvs.get(i):
size = str(len(v.uvs[i]))
- if u.unit==0 or force_unit0:
+ if u.unit==0:
out_file.write("texcoord"+size, *v.uvs[i])
else:
out_file.write("multitexcoord"+size, u.unit, *v.uvs[i])
out_file.write("indices", f.vertices[0].index, f.vertices[i-1].index, f.vertices[i].index)
out_file.end()
- if mesh.use_lines and mesh.lines:
+ if mesh.lines:
out_file.begin("batch", "LINES")
for l in mesh.lines:
out_file.write("indices", l.vertices[0].index, l.vertices[1].index)
out_file.end()
- if winding_test:
+ if mesh.winding_test:
out_file.write("winding", "COUNTERCLOCKWISE")
if progress:
progress.set_task("Done", 1.0, 1.0)
- for m in bmeshes:
- bpy.data.meshes.remove(m)
-
return mesh
+import bpy
import math
import mathutils
else:
self._medge = me
self.smooth = False
+ self.key = me.key
self.faces = []
def __getattr__(self, attr):
def __init__(self, mv):
if mv.__class__==Vertex:
self._mvert = mv._mvert
- self.co = mv.co
- self.normal = mv.normal
self.uvs = mv.uvs[:]
self.tan = mv.tan
self.bino = mv.bino
- self.group_weight_scale = mv.group_weight_scale
else:
self._mvert = mv
- self.co = mv.co
- self.normal = mv.normal
self.uvs = []
self.tan = None
self.bino = None
- self.group_weight_scale = 1
+ self.index = mv.index
+ self.co = mv.co
+ self.normal = mv.normal
self.flag = False
+ self.edges = []
self.faces = []
+ self.groups = mv.groups[:]
def __getattr__(self, attr):
return getattr(self._mvert, attr)
return cmp(self.index, other.index)
+class VertexGroup:
+ def __init__(self, base):
+ self._base = base
+ self.group = base.group
+ self.weight = base.weight
+
+ def __getattr__(self, attr):
+ return getattr(self._mvert, attr)
+
+
class Face:
def __init__(self, mf):
self._mface = mf
+ self.index = mf.index
self.edges = []
self.vertices = mf.vertices[:]
self.uvs = []
return e
raise KeyError("No edge %s"%(key,))
+ def other_edge(self, e, v):
+ for d in self.edges:
+ if d!=e and v in d.vertices:
+ return d
+
def get_neighbors(self):
neighbors = [e.other_face(self) for e in self.edges]
return list(filter(bool, neighbors))
class UvLayer:
- def __init__(self, l, t):
- self._layer = l
- self.uvtex = t
- self.name = self.uvtex.name
+ def __init__(self, arg):
+ if type(arg)==str:
+ self._layer = None
+ self.name = arg
+ else:
+ self._layer = arg
+ self.name = arg.name
+ self.uvs = [d.uv for d in self.data]
+
self.unit = None
self.hidden = False
+
dot = self.name.find('.')
if dot>=0:
ext = self.name[dot:]
def __getattr__(self, attr):
return getattr(self._layer, attr)
-class FakeUvLayer:
- def __init__(self, n):
- self.uvtex = None
- self.name = n
- self.unit = None
- self.hidden = False
class Mesh:
def __init__(self, m):
self._mesh = m
+ self.winding_test = m.winding_test
+ self.tbn_vecs = m.tbn_vecs
+ self.vertex_groups = m.vertex_groups
+
self.vertices = [Vertex(v) for v in self.vertices]
self.faces = [Face(f) for f in self.polygons]
+ self.edges = [Edge(e) for e in self.edges]
+ self.edge_map = {e.key: e for e in self.edges}
+ self.loops = self.loops[:]
self.materials = self.materials[:]
+ if self.use_uv=='NONE':
+ self.uv_layers = []
+ elif self.uv_layers:
+ self.uv_layers = [UvLayer(u) for u in self.uv_layers]
+ self.uv_layers = sorted([u for u in self.uv_layers if not u.hidden], key=(lambda u: (u.unit or 1000, u.name)))
+
+ if self.use_uv=='UNIT0':
+ self.uv_layers = [self.uv_layers[0]]
+
+ next_unit = max((u.unit+1 for u in self.uv_layers if u.unit is not None), default=0)
+ for u in self.uv_layers:
+ if not u.unit:
+ u.unit = next_unit
+ next_unit += 1
- self.uv_layers = [UvLayer(self.uv_layers[i], self.uv_textures[i]) for i in range(len(self.uv_layers))]
- self.assign_texture_units()
+ for v in self.vertices:
+ v.groups = [VertexGroup(g) for g in v.groups]
for f in self.faces:
if len(f.vertices)>4:
raise ValueError("Ngons are not supported")
+
f.vertices = [self.vertices[i] for i in f.vertices]
for v in f.vertices:
v.faces.append(f)
- for u in self.uv_layers:
- f.uvs.append([u.data[f.loop_indices[i]].uv for i in range(len(f.vertices))])
- if f.material_index<len(self.materials):
- mat = self.materials[f.material_index]
- if mat and mat.array_atlas:
- layer = (mat.array_layer,)
- print(f.uvs, layer)
- for i in range(len(f.uvs)):
- f.uvs[i] = [mathutils.Vector(tuple(u)+layer) for u in f.uvs[i]]
-
- self.edges = dict([(e.key, Edge(e)) for e in self.edges])
- for f in self.faces:
+
for k in f.edge_keys:
- e = self.edges[k]
- e.faces.append(self.faces[f.index])
+ e = self.edge_map[k]
+ e.faces.append(f)
f.edges.append(e)
- self.lines = [Line(e) for e in self.edges.values() if not e.faces]
- for l in self.lines:
- l.vertices = [self.vertices[i] for i in l.vertices]
+ for e in self.edges:
+ e.vertices = [self.vertices[i] for i in e.vertices]
+ for v in e.vertices:
+ v.edges.append(e)
- if self.use_auto_smooth:
- smooth_limit = math.cos(self.auto_smooth_angle)
+ if self.use_lines:
+ self.lines = [Line(e) for e in self.edges if not e.faces]
else:
- smooth_limit = -1
-
- for e in self.edges.values():
- e.vertices = [self.vertices[i] for i in e.vertices]
- e.check_smooth(smooth_limit)
+ self.lines = []
def __getattr__(self, attr):
return getattr(self._mesh, attr)
v.co = matrix*v.co
def splice(self, other):
+ if len(self.uv_layers)!=len(other.uv_layers):
+ raise ValueError("Meshes have incompatible UV layers")
+ for i, u in enumerate(self.uv_layers):
+ if u.name!=other.uv_layers[i].name:
+ raise ValueError("Meshes have incompatible UV layers")
+
material_map = []
for m in other.materials:
if m in self.materials:
material_map.append(len(self.materials))
self.materials.append(m)
+ for i, u in enumerate(self.uv_layers):
+ u.uvs += other.uv_layers[i].uvs
+
offset = len(self.vertices)
- for v in other.vertices:
+ self.vertices += other.vertices
+ for v in self.vertices[offset:]:
v.index += offset
- self.vertices.append(v)
+
+ loop_offset = len(self.loops)
+ self.loops += other.loops
offset = len(self.faces)
- for f in other.faces:
+ self.faces += other.faces
+ for f in self.faces[offset:]:
f.index += offset
+ f.loop_start += loop_offset
+ f.loop_indices = range(f.loop_start, f.loop_start+f.loop_total)
if other.materials:
f.material_index = material_map[f.material_index]
- self.faces.append(f)
- for e in other.edges.values():
+ offset = len(self.edges)
+ self.edges += other.edges
+ for e in self.edges[offset:]:
+ e.index += offset
e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index)
- self.edges[e.key] = e
+ self.edge_map[e.key] = e
self.lines += other.lines
- def flatten_faces(self):
- for f in self.faces:
- f.use_smooth = False
+ def prepare_smoothing(self, progress=None):
+ smooth_limit = -1
+ if self.smoothing=='NONE':
+ for f in self.faces:
+ f.use_smooth = False
- for e in self.edges.values():
- e.check_smooth(1)
+ smooth_limit = 1
+ elif self.use_auto_smooth:
+ smooth_limit = math.cos(self.auto_smooth_angle)
+
+ for e in self.edges:
+ e.check_smooth(smooth_limit)
+
+ self.split_vertices(self.find_smooth_group, progress)
+
+ if self.smoothing!='BLENDER':
+ self.compute_normals()
+
+ def prepare_vertex_groups(self, obj):
+ for v in self.vertices:
+ if v.groups:
+ weight_sum = sum(g.weight for g in v.groups)
+ v.groups = sorted(v.groups, key=(lambda g: g.weight), reverse=True)[:self.max_groups_per_vertex]
+ weight_scale = weight_sum/sum(g.weight for g in v.groups)
+ for g in v.groups:
+ g.weight *= weight_scale
+
+ if obj.parent and obj.parent.type=="ARMATURE":
+ armature = obj.parent.data
+ bone_indices = {b.name: i for i, b in enumerate(armature.bones)}
+ group_index_map = {i: i for i in range(len(obj.vertex_groups))}
+ for g in first_obj.vertex_groups:
+ if g.name in bone_indices:
+ group_index_map[g.index] = bone_indices[g.name]
+
+ for v in self.vertices:
+ for g in v.groups:
+ g.group = group_index_map[g.group]
+
+ def prepare_uv(self, obj, progress=None):
+ if obj.material_tex and self.use_uv!='NONE':
+ layer = UvLayer("material_tex")
+
+ if self.use_uv=='UNIT0':
+ self.uv_layers = [layer]
+ layer.unit = 0
+ else:
+ self.uv_layers.append(layer)
+ layer.unit = max((u.unit+1 for u in self.uv_layers if u.unit is not None), default=0)
+
+ layer.uvs = [None]*len(self.loops)
+ for f in self.faces:
+ uv = mathutils.Vector(((f.material_index+0.5)/len(self.materials), 0.5))
+ for i in f.loop_indices:
+ layer.uvs[i] = uv
+
+ array_uv_layers = [t.uv_layer for m in self.materials if m.array_atlas for t in m.texture_slots if t and t.texture_coords=='UV']
+ array_uv_layers = [u for u in self.uv_layers if u.name in array_uv_layers]
+
+ if array_uv_layers:
+ for f in self.faces:
+ layer = 0
+ if f.material_index<len(self.materials):
+ mat = self.materials[f.material_index]
+ if mat and mat.array_atlas:
+ layer = mat.array_layer
+
+ for l in array_uv_layers:
+ for i in f.loop_indices:
+ l.uvs[i] = mathutils.Vector((*l.uvs[i], layer))
- def assign_texture_units(self):
- # Assign texture units for any non-hidden UV layers that lack one
- units = [u.unit for u in self.uv_layers if u.unit is not None]
- if units:
- free_unit = max(units)+1
- else:
- free_unit = 0
- for u in self.uv_layers:
- if u.unit is None:
- if not u.hidden:
- u.unit = free_unit
- free_unit += 1
-
- def generate_material_uv(self):
- self.uv_layers.append(FakeUvLayer("material_tex"))
- self.assign_texture_units()
for f in self.faces:
- f.uvs.append([((f.material_index+0.5)/len(self.materials), 0.5)]*len(f.vertices))
+ for u in self.uv_layers:
+ f.uvs.append([u.uvs[i] for i in f.loop_indices])
+
+ tbn_layer_index = -1
+ if self.tbn_vecs:
+ uv_names = [u.name for u in self.uv_layers]
+ if self.tbn_uvtex in uv_names:
+ tbn_layer_index = uv_names.index(self.tbn_uvtex)
+ self.compute_tbn(tbn_layer_index)
+ self.split_vertices(self.find_uv_group, progress, tbn_layer_index)
+
+ for i in range(len(self.uv_layers)):
+ self.split_vertices(self.find_uv_group, progress, i)
+
+ for v in self.vertices:
+ if v.faces:
+ f = v.faces[0]
+ i = f.vertices.index(v)
+ v.uvs = [u[i] for u in f.uvs]
+ else:
+ v.uvs = [(0.0, 0.0)]*len(self.uv_layers)
def split_vertices(self, find_group_func, progress, *args):
groups = []
progress.set_progress(i*0.5/len(self.vertices))
for i in range(len(self.vertices)):
- if len(groups[i])==1:
- continue
-
for g in groups[i][1:]:
v = Vertex(self.vertices[i])
v.index = len(self.vertices)
self.vertices.append(v)
v_edges = []
- v_edge_keys = set()
- for f in g:
- for e in f.edges:
- if e.key in v_edge_keys or self.vertices[i] not in e.vertices:
- continue
-
- e_faces_in_g = [c for c in e.faces if c in g]
+ for e in self.vertices[i].edges:
+ e_faces_in_g = [f for f in e.faces if f in g]
+ if e_faces_in_g:
boundary = len(e_faces_in_g)<len(e.faces)
v_edges.append((e, boundary, e_faces_in_g))
- v_edge_keys.add(e.key)
for e, boundary, e_faces_in_g in v_edges:
if boundary:
ne = Edge(e)
- for c in e_faces_in_g:
- e.faces.remove(c)
- c.edges[c.edges.index(e)] = ne
- ne.faces.append(c)
+ ne.index = len(self.edges)
+ self.edges.append(ne)
+
+ for f in e_faces_in_g:
+ e.faces.remove(f)
+ f.edges[f.edges.index(e)] = ne
+ ne.faces.append(f)
e = ne
else:
- del self.edges[e.key]
+ del self.edge_map[e.key]
+ self.vertices[i].edges.remove(e)
+ v.edges.append(e)
e.vertices[e.vertices.index(self.vertices[i])] = v
e.key = make_edge_key(e.vertices[0].index, e.vertices[1].index)
- self.edges[e.key] = e
+ self.edge_map[e.key] = e
for f in g:
self.vertices[i].faces.remove(f)
if progress:
progress.set_progress(0.5+i*0.5/len(self.vertices))
- def split_smooth(self, progress=None):
- self.split_vertices(self.find_smooth_group, progress)
-
- def split_uv(self, index, progress=None):
- self.split_vertices(self.find_uv_group, progress, index)
-
def find_smooth_group(self, vertex, face):
face.flag = True
- queue = [face]
- for f in queue:
- for e in f.edges:
- other = e.other_face(f)
- if other not in vertex.faces:
- continue
+ edges = [e for e in face.edges if vertex in e.vertices]
+
+ group = [face]
+ for e in edges:
+ f = face
+ while e.smooth:
+ f = e.other_face(f)
+ if not f or f.flag:
+ break
- if e.smooth:
- if not other.flag:
- other.flag = True
- queue.append(other)
+ f.flag = True
+ group.append(f)
+ e = f.other_edge(e, vertex)
- return queue
+ return group
def find_uv_group(self, vertex, face, index):
uv = face.uvs[index][face.vertices.index(vertex)]
face.flag = True
+
group = [face]
for f in vertex.faces:
if not f.flag and f.uvs[index][f.vertices.index(vertex)]==uv:
f.flag = True
group.append(f)
+
return group
def compute_normals(self):
for v in self.vertices:
- if v.faces:
- v.normal = mathutils.Vector()
- for f in v.faces:
- fv = f.pivot_vertices(v)
- edge1 = fv[1].co-fv[0].co
- edge2 = fv[-1].co-fv[0].co
- if edge1.length and edge2.length:
- weight = 1
- if len(f.get_edge(fv[0], fv[1]).faces)==1:
- weight += 1
- if len(f.get_edge(fv[0], fv[-1]).faces)==1:
- weight += 1
- v.normal += f.normal*edge1.angle(edge2)*weight
- if v.normal.length:
- v.normal.normalize()
- else:
- v.normal = mathutils.Vector((0, 0, 1))
- else:
- # XXX Should use edges to compute normal
- v.normal = mathutils.Vector((0, 0, 1))
+ v.normal = mathutils.Vector()
+ for f in v.faces:
+ fv = f.pivot_vertices(v)
+ edge1 = fv[1].co-fv[0].co
+ edge2 = fv[-1].co-fv[0].co
+ if edge1.length and edge2.length:
+ v.normal += f.normal*edge1.angle(edge2)
- def compute_uv(self):
- for v in self.vertices:
- if v.faces:
- f = v.faces[0]
- i = f.vertices.index(v)
- v.uvs = [u[i] for u in f.uvs]
+ if v.normal.length:
+ v.normal.normalize()
else:
- v.uvs = [(0.0, 0.0)]*len(self.uv_layers)
+ v.normal = mathutils.Vector((0, 0, 1))
def compute_tbn(self, index):
- if not self.uv_layers:
- return
+ layer_uvs = self.uv_layers[index].uvs
for v in self.vertices:
v.tan = mathutils.Vector()
v.bino = mathutils.Vector()
for f in v.faces:
- fv = f.pivot_vertices(v)
- uv0 = fv[0].uvs[index]
- uv1 = fv[1].uvs[index]
- uv2 = fv[-1].uvs[index]
+ vi = f.vertices.index(v)
+ uv0 = layer_uvs[f.loop_indices[vi]]
+ uv1 = layer_uvs[f.loop_indices[vi+1]]
+ uv2 = layer_uvs[f.loop_indices[vi-1]]
du1 = uv1[0]-uv0[0]
du2 = uv2[0]-uv0[0]
dv1 = uv1[1]-uv0[1]
dv2 = uv2[1]-uv0[1]
- edge1 = fv[1].co-fv[0].co
- edge2 = fv[-1].co-fv[0].co
+ edge1 = f.vertices[vi+1].co-f.vertices[vi].co
+ edge2 = f.vertices[vi-1].co-f.vertices[vi].co
div = (du1*dv2-du2*dv1)
if div:
mul = edge1.angle(edge2)/div
if v.bino.length:
v.bino.normalize()
- def sort_vertex_groups(self, max_groups):
+ def drop_references(self):
for v in self.vertices:
- if v.groups:
- v.groups = sorted(v.groups, key=(lambda g: g.weight), reverse=True)
- v.group_weight_scale = 1.0/sum(g.weight for g in v.groups[:max_groups])
+ v._mvert = None
+ for g in v.groups:
+ g._base = None
+ for e in self.edges:
+ e._medge = None
+ for f in self.faces:
+ f._mface = None
+ for u in self.uv_layers:
+ u._layer = None
+ self._mesh = None
def create_strip(self, face, max_len):
# Find an edge with another unused face next to it
break
return result
+
+def create_mesh_from_object(context, obj, progress=None):
+ if obj.type!="MESH":
+ raise Exception("Object is not a mesh")
+
+ objs = [(obj, mathutils.Matrix())]
+ i = 0
+ while i<len(objs):
+ o, m = objs[i]
+ i += 1
+ for c in o.children:
+ if c.type=="MESH" and c.compound:
+ objs.append((c, m*c.matrix_local))
+
+ mesh = None
+ bmeshes = []
+ for o, m in objs:
+ bmesh = o.to_mesh(context.scene, True, "PREVIEW")
+ bmeshes.append(bmesh)
+ me = Mesh(bmesh)
+ me.transform(m)
+
+ if mesh:
+ mesh.splice(me)
+ else:
+ mesh = me
+
+ mesh.prepare_smoothing(progress)
+ mesh.prepare_vertex_groups(obj)
+ mesh.prepare_uv(obj, progress)
+
+ mesh.drop_references()
+ for m in bmeshes:
+ bpy.data.meshes.remove(m)
+
+ return mesh