mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-01-19 15:30:50 +00:00
Initial Blender 2.8 changes - it works, now it's time for improvements
This commit is contained in:
parent
8112c4e8b6
commit
ae86790e6c
3 changed files with 119 additions and 68 deletions
|
@ -24,11 +24,11 @@
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Quake MDL format",
|
"name": "Quake MDL format",
|
||||||
"author": "Bill Currie",
|
"author": "Bill Currie",
|
||||||
"blender": (2, 6, 3),
|
"blender": (2, 80, 0),
|
||||||
"api": 35622,
|
"api": 35622,
|
||||||
"location": "File > Import-Export",
|
"location": "File > Import-Export",
|
||||||
"description": "Import-Export Quake MDL (version 6) files. (.mdl)",
|
"description": "Import-Export Quake MDL (version 6) files. (.mdl)",
|
||||||
"warning": "not even alpha",
|
"warning": "still work in progress",
|
||||||
"wiki_url": "",
|
"wiki_url": "",
|
||||||
"tracker_url": "",
|
"tracker_url": "",
|
||||||
# "support": 'OFFICIAL',
|
# "support": 'OFFICIAL',
|
||||||
|
@ -64,28 +64,35 @@ EFFECTS=(
|
||||||
('EF_TRACER2', "Tracer 2", "Orange split trail + rotate"),
|
('EF_TRACER2', "Tracer 2", "Orange split trail + rotate"),
|
||||||
('EF_TRACER3', "Tracer 3", "Purple split trail"),
|
('EF_TRACER3', "Tracer 3", "Purple split trail"),
|
||||||
)
|
)
|
||||||
|
|
||||||
class QFMDLSettings(bpy.types.PropertyGroup):
|
|
||||||
'''
|
'''
|
||||||
eyeposition = FloatVectorProperty(
|
class QFMDLSettings(bpy.types.PropertyGroup):
|
||||||
name="Eye Position",
|
# eyeposition = FloatVectorProperty(
|
||||||
description="View possion relative to object origin")
|
# name="Eye Position",
|
||||||
synctype = EnumProperty(
|
# description="View possion relative to object origin")
|
||||||
items=SYNCTYPE,
|
# synctype = EnumProperty(
|
||||||
name="Sync Type",
|
# items=SYNCTYPE,
|
||||||
description="Add random time offset for automatic animations")
|
# name="Sync Type",
|
||||||
rotate = BoolProperty(
|
# description="Add random time offset for automatic animations")
|
||||||
name="Rotate",
|
# rotate = BoolProperty(
|
||||||
description="Rotate automatically (for pickup items)")
|
# name="Rotate",
|
||||||
effects = EnumProperty(
|
# description="Rotate automatically (for pickup items)")
|
||||||
items=EFFECTS,
|
# effects = EnumProperty(
|
||||||
name="Effects",
|
# items=EFFECTS,
|
||||||
description="Particle trail effects")
|
# name="Effects",
|
||||||
#doesn't work :(
|
# description="Particle trail effects")
|
||||||
#script = PointerProperty(
|
# #doesn't work :(
|
||||||
# type=bpy.types.Object,
|
# #script = PointerProperty(
|
||||||
# name="Script",
|
# # type=bpy.types.Object,
|
||||||
# description="Script for animating frames and skins")
|
# # name="Script",
|
||||||
|
# # description="Script for animating frames and skins")
|
||||||
|
|
||||||
|
# xform = BoolProperty(
|
||||||
|
# name="Auto transform",
|
||||||
|
# description="Auto-apply location/rotation/scale when exporting",
|
||||||
|
# default=True)
|
||||||
|
# md16 = BoolProperty(
|
||||||
|
# name="16-bit",
|
||||||
|
# description="16 bit vertex coordinates: QuakeForge only")
|
||||||
|
|
||||||
xform = BoolProperty(
|
xform = BoolProperty(
|
||||||
name="Auto transform",
|
name="Auto transform",
|
||||||
|
@ -94,10 +101,10 @@ class QFMDLSettings(bpy.types.PropertyGroup):
|
||||||
md16 = BoolProperty(
|
md16 = BoolProperty(
|
||||||
name="16-bit",
|
name="16-bit",
|
||||||
description="16 bit vertex coordinates: QuakeForge only")
|
description="16 bit vertex coordinates: QuakeForge only")
|
||||||
'''
|
|
||||||
script = StringProperty(
|
script = StringProperty(
|
||||||
name="Script",
|
name="Script",
|
||||||
description="Script for animating frames and skins")
|
description="Script for animating frames and skins")
|
||||||
|
'''
|
||||||
|
|
||||||
class ImportMDL6(bpy.types.Operator, ImportHelper):
|
class ImportMDL6(bpy.types.Operator, ImportHelper):
|
||||||
'''Load a Quake MDL (v6) File'''
|
'''Load a Quake MDL (v6) File'''
|
||||||
|
@ -155,6 +162,7 @@ class ExportMDL6(bpy.types.Operator, ExportHelper):
|
||||||
keywords = self.as_keywords (ignore=("check_existing", "filter_glob"))
|
keywords = self.as_keywords (ignore=("check_existing", "filter_glob"))
|
||||||
return export_mdl.export_mdl(self, context, **keywords)
|
return export_mdl.export_mdl(self, context, **keywords)
|
||||||
|
|
||||||
|
'''
|
||||||
class OBJECT_PT_MDLPanel(bpy.types.Panel):
|
class OBJECT_PT_MDLPanel(bpy.types.Panel):
|
||||||
bl_space_type = 'PROPERTIES'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
|
@ -176,6 +184,7 @@ class OBJECT_PT_MDLPanel(bpy.types.Panel):
|
||||||
layout.prop(obj.qfmdl, "script")
|
layout.prop(obj.qfmdl, "script")
|
||||||
layout.prop(obj.qfmdl, "xform")
|
layout.prop(obj.qfmdl, "xform")
|
||||||
layout.prop(obj.qfmdl, "md16")
|
layout.prop(obj.qfmdl, "md16")
|
||||||
|
'''
|
||||||
|
|
||||||
def menu_func_import(self, context):
|
def menu_func_import(self, context):
|
||||||
self.layout.operator(ImportMDL6.bl_idname, text="Quake MDL (.mdl)")
|
self.layout.operator(ImportMDL6.bl_idname, text="Quake MDL (.mdl)")
|
||||||
|
@ -184,21 +193,25 @@ def menu_func_import(self, context):
|
||||||
def menu_func_export(self, context):
|
def menu_func_export(self, context):
|
||||||
self.layout.operator(ExportMDL6.bl_idname, text="Quake MDL (.mdl)")
|
self.layout.operator(ExportMDL6.bl_idname, text="Quake MDL (.mdl)")
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
#OBJECT_PT_MDLPanel,
|
||||||
|
ImportMDL6,
|
||||||
|
ExportMDL6
|
||||||
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_module(__name__)
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
bpy.types.Object.qfmdl = PointerProperty(type=QFMDLSettings)
|
|
||||||
|
|
||||||
bpy.types.INFO_MT_file_import.append(menu_func_import)
|
|
||||||
bpy.types.INFO_MT_file_export.append(menu_func_export)
|
|
||||||
|
|
||||||
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||||
|
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.utils.unregister_module(__name__)
|
for cls in classes:
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
bpy.types.INFO_MT_file_import.remove(menu_func_import)
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||||
bpy.types.INFO_MT_file_export.remove(menu_func_export)
|
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
register()
|
||||||
|
|
|
@ -86,7 +86,7 @@ def null_skin(size):
|
||||||
return skin
|
return skin
|
||||||
|
|
||||||
def active_uv(mesh):
|
def active_uv(mesh):
|
||||||
for uvt in mesh.uv_textures:
|
for uvt in mesh.uv_layers:
|
||||||
if uvt.active:
|
if uvt.active:
|
||||||
return uvt
|
return uvt
|
||||||
return None
|
return None
|
||||||
|
@ -95,6 +95,18 @@ def make_skin(operator, mdl, mesh):
|
||||||
uvt = active_uv(mesh)
|
uvt = active_uv(mesh)
|
||||||
mdl.skinwidth, mdl.skinheight = (4, 4)
|
mdl.skinwidth, mdl.skinheight = (4, 4)
|
||||||
skin = null_skin((mdl.skinwidth, mdl.skinheight))
|
skin = null_skin((mdl.skinwidth, mdl.skinheight))
|
||||||
|
|
||||||
|
mat = bpy.context.object.data.materials[0]
|
||||||
|
allNodes = mat.node_tree.nodes
|
||||||
|
|
||||||
|
for node in allNodes:
|
||||||
|
if node.type == "TEX_IMAGE":
|
||||||
|
image = node.image
|
||||||
|
mdl.skinwidth, mdl.skinheight = image.size
|
||||||
|
skin = convert_image(image)
|
||||||
|
mdl.skins.append(skin)
|
||||||
|
|
||||||
|
'''
|
||||||
if (uvt and uvt.data and uvt.data[0].image):
|
if (uvt and uvt.data and uvt.data[0].image):
|
||||||
image = uvt.data[0].image
|
image = uvt.data[0].image
|
||||||
if (uvt.data[0].image.size[0] and uvt.data[0].image.size[1]):
|
if (uvt.data[0].image.size[0] and uvt.data[0].image.size[1]):
|
||||||
|
@ -103,7 +115,9 @@ def make_skin(operator, mdl, mesh):
|
||||||
else:
|
else:
|
||||||
operator.report({'WARNING'},
|
operator.report({'WARNING'},
|
||||||
"Texture '%s' invalid (missing?)." % image.name)
|
"Texture '%s' invalid (missing?)." % image.name)
|
||||||
|
|
||||||
mdl.skins.append(skin)
|
mdl.skins.append(skin)
|
||||||
|
'''
|
||||||
|
|
||||||
def build_tris(mesh):
|
def build_tris(mesh):
|
||||||
# mdl files have a 1:1 relationship between stverts and 3d verts.
|
# mdl files have a 1:1 relationship between stverts and 3d verts.
|
||||||
|
@ -183,7 +197,7 @@ def calc_average_area(mdl):
|
||||||
a = Vector(verts[0].r) - Vector(verts[1].r)
|
a = Vector(verts[0].r) - Vector(verts[1].r)
|
||||||
b = Vector(verts[2].r) - Vector(verts[1].r)
|
b = Vector(verts[2].r) - Vector(verts[1].r)
|
||||||
c = a.cross(b)
|
c = a.cross(b)
|
||||||
totalarea += (c * c) ** 0.5 / 2.0
|
totalarea += (c @ c) ** 0.5 / 2.0
|
||||||
return totalarea / len(mdl.tris)
|
return totalarea / len(mdl.tris)
|
||||||
|
|
||||||
def get_properties(
|
def get_properties(
|
||||||
|
@ -301,7 +315,8 @@ def export_mdl(
|
||||||
):
|
):
|
||||||
|
|
||||||
obj = context.active_object
|
obj = context.active_object
|
||||||
mesh = obj.to_mesh(context.scene, True, 'PREVIEW') #wysiwyg?
|
#mesh = obj.to_mesh(context.scene, True, 'PREVIEW') #wysiwyg?
|
||||||
|
mesh = obj.to_mesh(context.depsgraph, True, calc_undeformed=False)
|
||||||
#if not check_faces(mesh):
|
#if not check_faces(mesh):
|
||||||
# operator.report({'ERROR'},
|
# operator.report({'ERROR'},
|
||||||
# "Mesh has faces with more than 3 vertices.")
|
# "Mesh has faces with more than 3 vertices.")
|
||||||
|
@ -334,7 +349,7 @@ def export_mdl(
|
||||||
curframe = context.scene.frame_current
|
curframe = context.scene.frame_current
|
||||||
for fno in range(context.scene.frame_start, context.scene.frame_end + 1):
|
for fno in range(context.scene.frame_start, context.scene.frame_end + 1):
|
||||||
context.scene.frame_set(fno)
|
context.scene.frame_set(fno)
|
||||||
mesh = obj.to_mesh(context.scene, True, 'PREVIEW') #wysiwyg?
|
mesh = obj.to_mesh(context.depsgraph, True, calc_undeformed=False) #wysiwyg?
|
||||||
if xform:
|
if xform:
|
||||||
mesh.transform(mdl.obj.matrix_world)
|
mesh.transform(mdl.obj.matrix_world)
|
||||||
mdl.frames.append(make_frame(mesh, vertmap))
|
mdl.frames.append(make_frame(mesh, vertmap))
|
||||||
|
|
|
@ -39,7 +39,7 @@ def make_verts(mdl, framenum, subframenum=0):
|
||||||
( 0, 0,s.z,o.z),
|
( 0, 0,s.z,o.z),
|
||||||
( 0, 0, 0, 1)))
|
( 0, 0, 0, 1)))
|
||||||
for v in frame.verts:
|
for v in frame.verts:
|
||||||
verts.append(m * Vector(v.r))
|
verts.append(m @ Vector(v.r))
|
||||||
return verts
|
return verts
|
||||||
|
|
||||||
def make_faces(mdl):
|
def make_faces(mdl):
|
||||||
|
@ -87,7 +87,7 @@ def load_skins(mdl):
|
||||||
p[l + 2] = c[2] / 255.0
|
p[l + 2] = c[2] / 255.0
|
||||||
p[l + 3] = 1.0
|
p[l + 3] = 1.0
|
||||||
img.pixels[:] = p[:]
|
img.pixels[:] = p[:]
|
||||||
img.pack(True)
|
img.pack(as_png=True)
|
||||||
img.use_fake_user = True
|
img.use_fake_user = True
|
||||||
|
|
||||||
mdl.images=[]
|
mdl.images=[]
|
||||||
|
@ -100,27 +100,47 @@ def load_skins(mdl):
|
||||||
|
|
||||||
def setup_skins(mdl, uvs):
|
def setup_skins(mdl, uvs):
|
||||||
load_skins(mdl)
|
load_skins(mdl)
|
||||||
img = mdl.images[0] # use the first skin for now
|
# img = mdl.images[0] # use the first skin for now
|
||||||
uvlay = mdl.mesh.uv_textures.new(mdl.name)
|
# uvlay = mdl.mesh.uv_textures.new(mdl.name)
|
||||||
uvloop = mdl.mesh.uv_layers[0]
|
# uvloop = mdl.mesh.uv_layers[0]
|
||||||
for i, texpoly in enumerate(uvlay.data):
|
# for i, texpoly in enumerate(uvlay.data):
|
||||||
|
uvloop = mdl.mesh.uv_layers.new(name = mdl.name)
|
||||||
|
for i in range(len(mdl.mesh.polygons)):
|
||||||
poly = mdl.mesh.polygons[i]
|
poly = mdl.mesh.polygons[i]
|
||||||
mdl_uv = uvs[i]
|
mdl_uv = uvs[i]
|
||||||
texpoly.image = img
|
# texpoly.image = img # TODO: commented out by jazz
|
||||||
for j,k in enumerate(poly.loop_indices):
|
for j,k in enumerate(poly.loop_indices):
|
||||||
uvloop.data[k].uv = mdl_uv[j]
|
uvloop.data[k].uv = mdl_uv[j]
|
||||||
|
|
||||||
|
# Create main material
|
||||||
mat = bpy.data.materials.new(mdl.name)
|
mat = bpy.data.materials.new(mdl.name)
|
||||||
|
mat.blend_method = 'OPAQUE'
|
||||||
mat.diffuse_color = (1,1,1)
|
mat.diffuse_color = (1,1,1)
|
||||||
mat.use_raytrace = False
|
mat.metallic = 1
|
||||||
tex = bpy.data.textures.new(mdl.name, 'IMAGE')
|
mat.roughness = 1
|
||||||
tex.extension = 'CLIP'
|
mat.specular_intensity = 0
|
||||||
tex.use_preview_alpha = True
|
mat.use_nodes = True
|
||||||
tex.image = img
|
|
||||||
mat.texture_slots.add()
|
# TODO: turn transform to True and position it properly in editor
|
||||||
ts = mat.texture_slots[0]
|
emissionNode = mat.node_tree.nodes.new("ShaderNodeEmission")
|
||||||
ts.texture = tex
|
shaderOut = mat.node_tree.nodes["Material Output"]
|
||||||
ts.use_map_alpha = True
|
bdsf_node = mat.node_tree.nodes["Principled BSDF"]
|
||||||
ts.texture_coords = 'UV'
|
mat.node_tree.nodes.remove(bdsf_node)
|
||||||
|
|
||||||
|
#Add skingroup
|
||||||
|
#bpy.ops.object.material_slot_add()
|
||||||
|
#bpy.ops.material.new()
|
||||||
|
|
||||||
|
#Add all existing textures to shader node
|
||||||
|
for i in range(len(mdl.images)):
|
||||||
|
tex_node = mat.node_tree.nodes.new("ShaderNodeTexImage")
|
||||||
|
tex_node.image = mdl.images[i]
|
||||||
|
tex_node.interpolation = "Closest"
|
||||||
|
if i == 0:
|
||||||
|
# connect only first texture (we'll need something smarter in the future)
|
||||||
|
mat.node_tree.links.new(tex_node.outputs[0], emissionNode.inputs[0])
|
||||||
|
|
||||||
|
mat.node_tree.links.new(emissionNode.outputs[0], shaderOut.inputs[0])
|
||||||
mdl.mesh.materials.append(mat)
|
mdl.mesh.materials.append(mat)
|
||||||
|
|
||||||
def make_shape_key(mdl, framenum, subframenum=0):
|
def make_shape_key(mdl, framenum, subframenum=0):
|
||||||
|
@ -133,7 +153,7 @@ def make_shape_key(mdl, framenum, subframenum=0):
|
||||||
name = frame.name
|
name = frame.name
|
||||||
else:
|
else:
|
||||||
frame.name = name
|
frame.name = name
|
||||||
frame.key = mdl.obj.shape_key_add(name)
|
frame.key = mdl.obj.shape_key_add(name=name)
|
||||||
frame.key.value = 0.0
|
frame.key.value = 0.0
|
||||||
mdl.keys.append(frame.key)
|
mdl.keys.append(frame.key)
|
||||||
s = Vector(mdl.scale)
|
s = Vector(mdl.scale)
|
||||||
|
@ -143,11 +163,11 @@ def make_shape_key(mdl, framenum, subframenum=0):
|
||||||
( 0, 0,s.z,o.z),
|
( 0, 0,s.z,o.z),
|
||||||
( 0, 0, 0, 1)))
|
( 0, 0, 0, 1)))
|
||||||
for i, v in enumerate(frame.verts):
|
for i, v in enumerate(frame.verts):
|
||||||
frame.key.data[i].co = m * Vector(v.r)
|
frame.key.data[i].co = m @ Vector(v.r)
|
||||||
|
|
||||||
def build_shape_keys(mdl):
|
def build_shape_keys(mdl):
|
||||||
mdl.keys = []
|
mdl.keys = []
|
||||||
mdl.obj.shape_key_add("Basis")
|
mdl.obj.shape_key_add(name="Basis",from_mix=False)
|
||||||
mdl.mesh.shape_keys.name = mdl.name
|
mdl.mesh.shape_keys.name = mdl.name
|
||||||
mdl.obj.active_shape_key_index = 0
|
mdl.obj.active_shape_key_index = 0
|
||||||
for i, frame in enumerate(mdl.frames):
|
for i, frame in enumerate(mdl.frames):
|
||||||
|
@ -327,6 +347,7 @@ def parse_flags(flags):
|
||||||
else:
|
else:
|
||||||
return 'EF_NONE'
|
return 'EF_NONE'
|
||||||
|
|
||||||
|
'''
|
||||||
def set_properties(mdl):
|
def set_properties(mdl):
|
||||||
mdl.obj.qfmdl.eyeposition = mdl.eyeposition
|
mdl.obj.qfmdl.eyeposition = mdl.eyeposition
|
||||||
try:
|
try:
|
||||||
|
@ -337,12 +358,13 @@ def set_properties(mdl):
|
||||||
mdl.obj.qfmdl.effects = parse_flags(mdl.flags)
|
mdl.obj.qfmdl.effects = parse_flags(mdl.flags)
|
||||||
mdl.obj.qfmdl.script = mdl.text.name #FIXME really want the text object
|
mdl.obj.qfmdl.script = mdl.text.name #FIXME really want the text object
|
||||||
mdl.obj.qfmdl.md16 = (mdl.ident == "MD16")
|
mdl.obj.qfmdl.md16 = (mdl.ident == "MD16")
|
||||||
|
'''
|
||||||
|
|
||||||
def import_mdl(operator, context, filepath):
|
def import_mdl(operator, context, filepath, **opts):
|
||||||
bpy.context.user_preferences.edit.use_global_undo = False
|
bpy.context.preferences.edit.use_global_undo = False
|
||||||
|
|
||||||
for obj in bpy.context.scene.objects:
|
for obj in bpy.context.scene.collection.objects:
|
||||||
obj.select = False
|
obj.select_set(False)
|
||||||
|
|
||||||
mdl = MDL()
|
mdl = MDL()
|
||||||
if not mdl.read(filepath):
|
if not mdl.read(filepath):
|
||||||
|
@ -354,18 +376,19 @@ def import_mdl(operator, context, filepath):
|
||||||
mdl.mesh = bpy.data.meshes.new(mdl.name)
|
mdl.mesh = bpy.data.meshes.new(mdl.name)
|
||||||
mdl.mesh.from_pydata(verts, [], faces)
|
mdl.mesh.from_pydata(verts, [], faces)
|
||||||
mdl.obj = bpy.data.objects.new(mdl.name, mdl.mesh)
|
mdl.obj = bpy.data.objects.new(mdl.name, mdl.mesh)
|
||||||
bpy.context.scene.objects.link(mdl.obj)
|
|
||||||
bpy.context.scene.objects.active = mdl.obj
|
bpy.context.scene.collection.objects.link(mdl.obj)
|
||||||
mdl.obj.select = True
|
mdl.obj.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = mdl.obj
|
||||||
setup_skins(mdl, uvs)
|
setup_skins(mdl, uvs)
|
||||||
if len(mdl.frames) > 1 or mdl.frames[0].type:
|
if len(mdl.frames) > 1 or mdl.frames[0].type:
|
||||||
build_shape_keys(mdl)
|
build_shape_keys(mdl)
|
||||||
merge_frames(mdl)
|
merge_frames(mdl)
|
||||||
build_actions(mdl)
|
build_actions(mdl)
|
||||||
write_text(mdl)
|
write_text(mdl)
|
||||||
set_properties(mdl)
|
#set_properties(mdl) #TODO: bring it back
|
||||||
|
|
||||||
mdl.mesh.update()
|
mdl.mesh.update()
|
||||||
|
|
||||||
bpy.context.user_preferences.edit.use_global_undo = True
|
bpy.context.preferences.edit.use_global_undo = True
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
Loading…
Reference in a new issue