Added Blender export support.
bugfix: geo.py no longer crashes when writing models with no triangles.master
parent
be2d6f5eed
commit
af369f9b31
@ -0,0 +1,93 @@
|
||||
|
||||
bl_info = {
|
||||
"name": "City of Heroes (.geo)",
|
||||
"author": "TigerKat",
|
||||
"version": (0, 1),
|
||||
"blender": (2, 79, 0),
|
||||
"location": "File > Import/Export,",
|
||||
"description": "City of Heroes (.geo)",
|
||||
"tracker_url": "https://git.ourodev.com/tigerkat/geopy/issues",
|
||||
"warning": "",
|
||||
"category": "Import-Export"}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
if "import_geo" in locals():
|
||||
importlib.reload(import_geo)
|
||||
if "export_geo" in locals():
|
||||
importlib.reload(export_geo)
|
||||
if "geo" in locals():
|
||||
importlib.reload(geo)
|
||||
if "geomesh" in locals():
|
||||
importlib.reload(geomesh)
|
||||
if "vec_math" in locals():
|
||||
importlib.reload(vec_math)
|
||||
## Python doesn't reload package sub-modules at the same time as __init__.py!
|
||||
#import os.path
|
||||
#import imp, sys
|
||||
#for filename in [ f for f in os.listdir(os.path.dirname(os.path.realpath(__file__))) if f.endswith(".py") ]:
|
||||
# if filename == os.path.basename(__file__): continue
|
||||
# mod = sys.modules.get("{}.{}".format(__name__,filename[:-3]))
|
||||
# if mod: imp.reload(mod)
|
||||
|
||||
import mathutils
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
StringProperty,
|
||||
)
|
||||
|
||||
from bpy_extras.io_utils import (
|
||||
ImportHelper,
|
||||
ExportHelper,
|
||||
)
|
||||
|
||||
##############################################################################
|
||||
|
||||
class ImportGeo(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = "import_scene.geo"
|
||||
bl_label = "Import GEO"
|
||||
|
||||
filename_ext = ".geo"
|
||||
filter_glob = StringProperty(default="*.geo", options={'HIDDEN'})
|
||||
def execute(self, context):
|
||||
from . import import_geo
|
||||
keywords = self.as_keywords(ignore=("filter_glob",))
|
||||
return import_geo.load(self, context, **keywords)
|
||||
|
||||
class ExportGeo(bpy.types.Operator, ExportHelper):
|
||||
bl_idname = "export_scene.geo"
|
||||
bl_label = "Export GEO"
|
||||
|
||||
filename_ext = ".geo"
|
||||
filter_glob = StringProperty(default="*.geo", options={'HIDDEN'})
|
||||
def execute(self, context):
|
||||
from . import export_geo
|
||||
keywords = self.as_keywords(ignore=("filter_glob",
|
||||
"check_existing",
|
||||
))
|
||||
return export_geo.save(self, context, **keywords)
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(ImportGeo.bl_idname,
|
||||
text="City of Heroes (.geo)")
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(ExportGeo.bl_idname,
|
||||
text="City of Heroes (.geo)")
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.INFO_MT_file_import.append(menu_func_import)
|
||||
bpy.types.INFO_MT_file_export.append(menu_func_export)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.INFO_MT_file_import.remove(menu_func_import)
|
||||
bpy.types.INFO_MT_file_export.remove(menu_func_export)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
@ -0,0 +1,110 @@
|
||||
from .geo import Geo
|
||||
from .geomesh import *
|
||||
import bpy.path
|
||||
import bpy
|
||||
|
||||
from bpy_extras.io_utils import axis_conversion
|
||||
|
||||
def convert_mesh(geo_model, mesh, obj):
|
||||
# Be sure tessface & co are available!
|
||||
if not mesh.tessfaces and mesh.polygons:
|
||||
mesh.calc_tessface()
|
||||
|
||||
mesh_verts = mesh.vertices # save a lookup
|
||||
|
||||
has_uv = bool(mesh.tessface_uv_textures)
|
||||
if has_uv:
|
||||
active_uv_layer = mesh.tessface_uv_textures.active
|
||||
if not active_uv_layer:
|
||||
has_uv = False
|
||||
else:
|
||||
active_uv_layer = active_uv_layer.data
|
||||
|
||||
geomesh = GeoMesh()
|
||||
|
||||
texture_name = "white"
|
||||
print("len(mesh.tessfaces): %s" % len(mesh.tessfaces))
|
||||
print("mesh.tessfaces: %s" % repr(mesh.tessfaces))
|
||||
for i, f in enumerate(mesh.tessfaces):
|
||||
if has_uv:
|
||||
uv = active_uv_layer[i]
|
||||
texture_image = uv.image
|
||||
uv = [uv.uv1, uv.uv2, uv.uv3, uv.uv4]
|
||||
if texture_image is None:
|
||||
texture_name = "white"
|
||||
else:
|
||||
texture_name = texture_image.name
|
||||
if texture_name == "":
|
||||
texture_name = bpy.path.display_name_from_filepath(texture_image.filepath)
|
||||
if texture_name == "":
|
||||
texture_name = "white"
|
||||
else:
|
||||
uv = [(0, 0)] * 4
|
||||
texture_name = "white"
|
||||
f_verts = f.vertices
|
||||
verts = []
|
||||
norms = []
|
||||
groups = []
|
||||
geoverts = []
|
||||
for i, v_index in enumerate(f_verts):
|
||||
v = mesh_verts[v_index]
|
||||
verts.append(v.co)
|
||||
norms.append(v.normal)
|
||||
weights = []
|
||||
for weight in v.groups:
|
||||
group = obj.vertex_groups[weight.group]
|
||||
w = [group.name, weight.weight]
|
||||
weights.append(w)
|
||||
gv = GeoVertex(v.co, v.normal, uv[i], weights)
|
||||
geoverts.append(gv)
|
||||
geomesh.addFace(geoverts, texture_name)
|
||||
|
||||
print("face: vertices: %s uvs: %s norms: %s groups: %s" % (verts, uv, norms, weights))
|
||||
geomesh.dump()
|
||||
geo_model.loadFromGeoMesh(geomesh)
|
||||
#todo:
|
||||
pass
|
||||
|
||||
def save(operator, context, filepath = "", global_matrix = None, use_mesh_modifiers = True):
|
||||
print("export_geo.save(): %s" % (filepath, ))
|
||||
|
||||
geo = Geo()
|
||||
geo.getTextureIndex("white.tga")
|
||||
geo.getTextureIndex("white")
|
||||
|
||||
body_name = bpy.path.display_name_from_filepath(filepath)
|
||||
geo.setName(body_name)
|
||||
|
||||
axis_rotation = axis_conversion('-Y', 'Z', 'Z', 'Y')
|
||||
axis_rotation.resize_4x4()
|
||||
|
||||
for ob in context.selected_objects:
|
||||
print("Object: %s (%s)" % (ob.name, ob.type))
|
||||
if ob.type != "MESH":
|
||||
continue
|
||||
ob.update_from_editmode()
|
||||
|
||||
if global_matrix is None:
|
||||
from mathutils import Matrix
|
||||
global_matrix = Matrix()
|
||||
|
||||
# get the modifiers
|
||||
mesh = ob.to_mesh(bpy.context.scene, use_mesh_modifiers, "PREVIEW")
|
||||
|
||||
#translate_matrix = Matrix.Translation(-ob.location)
|
||||
translate_matrix = Matrix()
|
||||
# * ob.matrix_world
|
||||
mesh.transform(global_matrix * translate_matrix * axis_rotation)
|
||||
|
||||
mesh.calc_normals()
|
||||
|
||||
geo_model = geo.addModel(ob.name)
|
||||
|
||||
convert_mesh(geo_model, mesh, ob)
|
||||
|
||||
data = geo.saveToData()
|
||||
fh = open(filepath, "wb")
|
||||
fh.write(data)
|
||||
fh.close()
|
||||
|
||||
return {'FINISHED'}
|
@ -0,0 +1,128 @@
|
||||
import functools
|
||||
|
||||
def weight_cmp(a, b):
|
||||
return a[1] < b[1] or (a[1] == b[1] and a[0] < b[0])
|
||||
|
||||
def tuple_weights(weights):
|
||||
return tuple((tuple(w) for w in weights))
|
||||
|
||||
class GeoVertex:
|
||||
def __init__(self, coord, normal, uv, weights):
|
||||
self.coord = coord
|
||||
self.normal = normal
|
||||
self.uv = uv
|
||||
self.weights = weights
|
||||
def __eq__(self, other):
|
||||
return self.coord == other.coord and self.normal == other.normal and self.uv == other.uv and self.weights == other.weights
|
||||
def __hash__(self):
|
||||
#print("__hash__: %s" % ((tuple(self.coord), tuple(self.normal), tuple(self.uv), tuple(self.weights)), ))
|
||||
return hash((tuple(self.coord), tuple(self.normal), tuple(self.uv), tuple_weights(self.weights)))
|
||||
def selectWeights(self, count = None):
|
||||
"""Returns the list of weights attached to this vertex. List is sorted by weight, with the strongest first. If 'count' is given, only 'count' strongest are return. The final list is normalized so the sum is 1."""
|
||||
if len(self.weights) <= 0:
|
||||
return []
|
||||
weights = list(self.weights)
|
||||
#sort by weight
|
||||
weights.sort(key = functools.cmp_to_key(weight_cmp), reverse = True)
|
||||
if count is not None and len(weights) > count:
|
||||
weights = weights[0:count]
|
||||
nw = 0.0
|
||||
for w in weights:
|
||||
nw += w[1]
|
||||
if nw <= 0:
|
||||
for w in weights:
|
||||
w[1] = 0
|
||||
weights[0][1] = 1
|
||||
else:
|
||||
for w in weights:
|
||||
w[1] /= nw
|
||||
return weights
|
||||
def dump(self):
|
||||
print(" GeoVertex: coord: %s normal: %s uv: %s weights: %s" % (self.coord, self.normal, self.uv, self.weights))
|
||||
class GeoFace:
|
||||
def __init__(self, vert_indexes, texture_index):
|
||||
self.vert_indexes = vert_indexes
|
||||
self.texture_index = texture_index
|
||||
def __eq__(self, other):
|
||||
return self.vert_indexes == other.vert_indexes and self.texture_index == other.texture_index
|
||||
def dump(self):
|
||||
print(" GeoFace: vertex indexes: %s texture index: %s" % (self.vert_indexes, self.texture_index))
|
||||
class GeoMesh:
|
||||
def __init__(self):
|
||||
self.geovertex = []
|
||||
self.geovertex_map = {}
|
||||
self.textures = []
|
||||
self.textures_map = {}
|
||||
self.weights = []
|
||||
self.weights_map = {}
|
||||
self.face = []
|
||||
self.have_weights = False
|
||||
self.have_uvs = True
|
||||
def getGeoVertexIndex(self, gv):
|
||||
index = self.geovertex_map.get(gv, len(self.geovertex))
|
||||
if index == len(self.geovertex):
|
||||
self.geovertex_map[gv] = index
|
||||
self.geovertex.append(gv)
|
||||
return index
|
||||
def getTextureIndex(self, name):
|
||||
index = self.textures_map.get(name, len(self.textures))
|
||||
if index == len(self.textures):
|
||||
self.textures_map[name] = index
|
||||
self.textures.append(name)
|
||||
return index
|
||||
def getWeightIndex(self, name):
|
||||
index = self.weights_map.get(name, len(self.weights))
|
||||
if index == len(self.weights):
|
||||
self.weights_map[name] = index
|
||||
self.weights.append(name)
|
||||
return index
|
||||
|
||||
def addFace(self, geovertices, texture_name):
|
||||
l = len(geovertices)
|
||||
if l > 3:
|
||||
#Do a naive conversion to a triangle fan, add each of those triangles as a face. Will give bad results in shape is not convex.
|
||||
#Choose the start point as the one closest to the origin. Ties are resolved by lexical comparison of the coordinates.
|
||||
start = 0
|
||||
start_dist = geovertices[0].coord.magnitude
|
||||
for i in range(1, len(geovertices)):
|
||||
dist = geovertices[i].coord.magnitude
|
||||
if dist < start_dist:
|
||||
start = i
|
||||
start_dist = dist
|
||||
elif dist == start_dist:
|
||||
for j in range(3):
|
||||
if geovertices[i].coord[j] < geovertices[start].coord[j]:
|
||||
start = i
|
||||
start_dist = dist
|
||||
break
|
||||
for i in range(2, len(geovertices)):
|
||||
i1 = (start + i - 1) % l
|
||||
i2 = (start + i) % l
|
||||
self.addFace([geovertices[start], geovertices[i1], geovertices[i2]], texture_name)
|
||||
return
|
||||
elif l < 3:
|
||||
return
|
||||
for i in range(3):
|
||||
for w in geovertices[i].weights:
|
||||
w[0] = self.getWeightIndex(w[0])
|
||||
self.have_weights = True
|
||||
geovertices_index = [self.getGeoVertexIndex(geovertices[0]),
|
||||
self.getGeoVertexIndex(geovertices[1]),
|
||||
self.getGeoVertexIndex(geovertices[2])]
|
||||
self.face.append(GeoFace(geovertices_index, self.getTextureIndex(texture_name)))
|
||||
pass
|
||||
def sortFaces(self):
|
||||
#Sort faces so they're grouped by texture index.
|
||||
#todo:
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
print("GeoMesh:")
|
||||
print(" Textures: %s" % (self.textures, ))
|
||||
print(" Weights: %s" % (self.weights, ))
|
||||
print(" Vertices:")
|
||||
for i, v in enumerate(self.geovertex):
|
||||
v.dump()
|
||||
print(" Faces:")
|
||||
for i, f in enumerate(self.face):
|
||||
f.dump()
|
@ -0,0 +1 @@
|
||||
|
Loading…
Reference in New Issue