Jump to content
The Dark Mod Forums

New 3d modeller wants to help


janexx

Recommended Posts

Hello janexx, and welcome! Awesome to hear you want to contribute to TDM. Took a look at your work, amazing stuff! Loved the sensibility and calm beauty of each scene. Im sure people here will go crazy with those cityscapes, that interior, foggy shot - I quite liked the Fox-thief, and that moving-city robot, that one is pretty fantastic.

I have done some modelling here and the places which helped me the most to get a picture of how to start were the wiki, for reference (http://wiki.thedarkmod.com/index.php?title=Modeling) and Katsbits, for modeling for games, http://www.katsbits.com/tutorials/#blender_tutorials .

The statues you linked look very good, and starting with "static models" like that would be the best, so you can work on the mesh and getting it in game eventually, without having to worry with other complex stuff like animation.

There are a number of steps you would need to take as you get further with your model, like shadow and collision meshes, and how to set up materials using your own textures (if you choose to use new ones not found in the game), but those are nothing complicated as you'll see and there are a number of people here that would be happy to help once you get there.

In the meantime, that are a lot of great missions around here and I suggest playing a few of them if you havent already, some great fun to be had. ;)

Edited by RPGista
Link to comment
Share on other sites

Thank you ver much for your very kind feedback! I feel flattered. :rolleyes:

Yes, I read already about shadow and collision meshes on your wiki page. And where are all the files stored / where will I put my model? I noticed the wiki page "Model Folder Structure", but where is this folder structure?


I already played some darkmod missions. It's really cool. Can't wait to see models of me in a mission. :P
  • Like 1
Link to comment
Share on other sites

Yeah, its been a while... Im linking a model I made, a warhammer. In it, you can see how the files that make up a "game model" are sorted, in terms of folder structure. Its gonna contain things you dont need to know right now, like def and material files (the first is to set up the entity, if its moveable or not, etc, the second to set up textures and how they work in game, if its wood, or metal, etc). Basicly, I've always submitted models in this manner, because this is how TDM works, in a kind of add-on system - a map is a series of files that will get temporarily unpacked on the game's own folders, they also have to be organized in this manner, just as a new sound, a texture, or a model will also have to be in their own default folder structure so the game will be able to locate and read them.

 

As you work, you dont need to worry about this set up at all. Apart from keeping material and texture names game friendly (like lower case, words separated by _ instead of spaces, things like that, I seem to remember blender's ase exporter being specially picky regarding names), all of this will only really matter when its time to pack up the finished model's file and textures and set up the other TDM related things.

 

The warhammer: https://www.sendspace.com/file/n1nqek

Edited by RPGista
Link to comment
Share on other sites

Nice work there! I hope you can use your talent to enhance the TDM experience for the players! It's really easy if you got the workflow once. I even made some simple models for my first map and imported them to TDM with no experience before. So I'm sure you can do it too! :)

  • Like 1

"Einen giftigen Trank aus Kräutern und Wurzeln für die närrischen Städter wollen wir brauen." - Text aus einem verlassenen Heidenlager

Link to comment
Share on other sites

Ok, I found in my local files the zip with the models. But how do you share files? Do you use something like svn, git...? Sorry, but what is Springheel and Dark Radiant? Do I have do install doom3 engine?

 

As I said, I use Blender. I can not open or export .lWO or .ASE files, but I saw in the wiki that there were linked some pages where should be scripts for that. I will see.

 

Thanks for the warhammer example. Thanks @SeriousToni, I hope so!

Edited by janexx
  • Like 1
Link to comment
Share on other sites

You'll want to test the model ingame before releasing it, not only to see if its actually working ok, but also to judge proportions and how the texture, normal and speculars are looking. Luckily this is really simple to do, as the editor Darkradiant is easy to use and all you need is a box room, a couple of lights, a player start, your model, and you can already play that in game.

Edited by RPGista
Link to comment
Share on other sites

Large statues can be up to 10000 polys as long as you can include a shadowmesh of around 1000 polys, though it's better to keep it lower than that if you can. If you want something like the reference pictures you posted, you'll probably need to start with a high poly version to create a normalmap from.

Link to comment
Share on other sites

 

you'll probably need to start with a high poly version to create a normalmap from.

yes, that's my plan

 

Ok, thaks for now! But I still don't know where to put my finished models/files later. Is there a place where you host your TDM-files?

  • Like 1
Link to comment
Share on other sites

The team uses a SVN while others simply post links to filesharing sites. Most use zippyshare - personally I use Dropbox because I think it has a clean interface, has folders and holds on to files a long time.

  • Like 1
Link to comment
Share on other sites

  • 3 months later...

Lovely! Is there mesh beneath that cloth wrap? If so, it would allow for the possibility of making it visible or invisible using skins. Not a big deal if it doesn't, but it will look a little repetitive if every statue has cloth wrapped around it in exactly the same way (though come to think of it, an alternate skin could colour the wrap with the same stone texture so it looks like part of the statue).

 

I can't help with the Linux issues, but if you want to send me the files I could put them in TDM so you can check the size.

Link to comment
Share on other sites

Thanks! I modelled the statue and then the cloth above, so there is a mesh beneath that cloth wrap. I can make them seperate. And I am not yet completely finished, I still have to bake the textures.

 

About the cloth: This Rendering was for a contest. I made the model again completely with stone material:

thumb_6436306darkmod_statue.png

 

I thought that would be better for the game.

 

Ok, how should I send you a file? And is .blend file ok or what file format shoud I use?

Edited by janexx
  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...

Need help!! I tried to export my model from Blender to an ase-file. But the ASE-export didn't work. I found a script. But it throws an error. Can anyone please export my model to ASE or LWO?

Or has anyone a good python script for exporting from blender? I use Blender 2.76. You can send me a PN.

  • Like 1
Link to comment
Share on other sites

How's this?

 

 

 

#
# +---------------------------------------------------------+
# | Copyright (c) 2002 Anthony D'Agostino					|
# | http://www.redrival.com/scorpius						|
# | scorpius@netzero.com									|
# | April 21, 2002											|
# | Read and write LightWave Object File Format (*.lwo)		|
# +---------------------------------------------------------+

# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA	 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****


"""\
This script exports self.meshes to LightWave file format.

LightWave is a full-featured commercial modeling and rendering
application. The lwo file format is composed of 'chunks,' is well
defined, and easy to read and write. It is similar in structure to the
trueSpace cob format.

Usage:<br>
	Select self.meshes to be exported and run this script from "File->Export" menu.

Supported:<br>
	UV Coordinates, Meshes, Materials, Material Indices, Specular
Highlights, and Vertex Colors. For added functionality, each object is
placed on its own layer. Someone added the CLIP chunk and imagename support.

Missing:<br>
	Not too much, I hope! .

Known issues:<br>
	Empty objects crash has been fixed.

Notes:<br>
	For compatibility reasons, it also reads lwo files in the old LW
v5.5 format.
"""


bl_info = {
	"name": "Export LightWave(.lwo)",
	"author": "Anthony D'Agostino (Scorpius) and Gert De Roost",
	"version": (2, 3, 2),
	"blender": (2, 69, 0),
	"location": "File > Export",
	"description": "Lightwave .lwo export",
	"warning": "",
	"wiki_url": "",
	"tracker_url": "",
	"category": "Import-Export"}


import bpy, bmesh
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, FloatProperty, EnumProperty
from bpy.app.handlers import persistent
import os, math, functools
try: import struct
except: struct = None
try: import io
except: io = None
try: import operator
except: operator = None




bpy.types.Material.vcmenu = EnumProperty(
			items = [("<none>", "<none>", "<none>")],
			name = "Vertex Color Map",
			description = "LWO export: vertex color map for this material",
			default = "<none>")
			


class idTechVertexColors(bpy.types.Panel):
	bl_label = "LwoExport Vertex Color Map"
	bl_space_type = "PROPERTIES"
	bl_region_type = "WINDOW"
	bl_context = "material"

	@classmethod
	def poll(self, context):
		return context.active_object.active_material!=None

	def draw(self, context):
		layout = self.layout
		layout.prop(context.active_object.active_material, 'vcmenu')


class MessageOperator(bpy.types.Operator):
	bl_idname = "lwoexport.message"
	bl_label = "Saved"

	def invoke(self, context, event):
	
		wm = context.window_manager
		return wm.invoke_popup(self, width=500, height=20)

	def draw(self, context):

		layout = self.layout
		row = layout.row()
		row.label(text = '', icon = "ERROR")
		row.label("Error | This exporter requires a full python installation")


class LwoExport(bpy.types.Operator, ExportHelper):
	bl_idname = "export.lwo"
	bl_label = "LwoExport"
	bl_description = "Export Lightwave .lwo file"
	bl_options = {"REGISTER"}
	filename_ext = ".lwo"
	filter_glob = StringProperty(default = "*.lwo", options = {'HIDDEN'})

	filepath = StringProperty( 
		name = "File Path",
		description = "File path used for exporting the .lwo file",
		maxlen = 1024,
		default = "" )

	option_idtech = BoolProperty( 
			name = "idTech compatible",
			description = "Saves .lwo compatible with idTech engines",
			default = False )

	option_smooth = BoolProperty( 
			name = "Smoothed",
			description = "Save entire mesh as smoothed",
			default = False )

	option_subd = BoolProperty( 
			name = "Export as subpatched",
			description = "Export mesh data as subpatched",
			default = False )

	option_applymod = BoolProperty( 
			name = "Apply modifiers",
			description = "Applies modifiers before exporting",
			default = True )

	option_triangulate = BoolProperty( 
			name = "Triangulate",
			description = "Triangulates all exportable objects",
			default = False )

	option_normals = BoolProperty( 
			name = "Recalculate Normals",
			description = "Recalculate normals before exporting",
			default = True )

	option_remove_doubles = BoolProperty( 
			name = "Remove Doubles",
			description = "Remove any duplicate vertices before exporting",
			default = True )

	option_apply_scale = BoolProperty( 
			name = "Scale",
			description = "Apply scale transformation",
			default = True )

	option_apply_location = BoolProperty( 
			name = "Location",
			description = "Apply location transformation",
			default = True )

	option_apply_rotation = BoolProperty( 
			name = "Rotation",
			description = "Apply rotation transformation",
			default = True )

	option_batch = BoolProperty( 
			name = "Batch Export",
			description = "A separate .lwo file for every selected object",
			default = False )

	option_normaddon = BoolProperty( 
			name = "Use \"Recalc Vert Normals\" addon data",
			description = "Export the vertex normals created with the \"Recalc Vert Normals\" addon",
			default = False )

	option_scale = FloatProperty( 
			name = "Scale",
			description = "Object scaling factor (default: 1.0)",
			min = 0.01,
			max = 1000.0,
			soft_min = 0.01,
			soft_max = 1000.0,
			default = 1.0 )
	
	def draw( self, context ):
		layout = self.layout

		box = layout.box()
		box.label( 'Essentials:' )
		box.prop( self, 'option_idtech' )
		box.prop( self, 'option_applymod' )
		box.prop( self, 'option_subd' )
		box.prop( self, 'option_triangulate' )
		box.prop( self, 'option_normals' )
		box.prop( self, 'option_remove_doubles' )
		box.prop( self, 'option_smooth' )
		box.label( "Transformations:" )
		box.prop( self, 'option_apply_scale' )
		box.prop( self, 'option_apply_rotation' )
		box.prop( self, 'option_apply_location' )
		box.label( "Advanced:" )
		box.prop( self, 'option_scale' )
		box.prop( self, 'option_batch')
		if 'vertex_normal_list' in context.active_object:
			box.prop( self, 'option_normaddon')
		
	@classmethod
	def poll(cls, context):
		obj = context.active_object
		return (obj and obj.type == 'MESH')

	def execute(self, context):
	
		global main
		
		main = self
	
		self.context = context
		self.VCOL_NAME = "Per-Face Vertex Colors"
		self.DEFAULT_NAME = "Blender Default"
		
		if struct and io and operator:
			self.write(self.filepath)
		else:
			bpy.ops.lwoexport.message('INVOKE_DEFAULT')
		

		return {'FINISHED'}

	# ==============================
	# === Write LightWave Format ===
	# ==============================
	def write(self, filename):
		objects = list(self.context.selected_objects)
		actobj = self.context.active_object
		
		try:	objects.sort( key = lambda a: a.name )
		except: objects.sort(lambda a,b: cmp(a.name, b.name))
	
		self.meshes = []
		object_name_lookup_orig = {}
		mesh_object_name_lookup = {} # for name lookups only
		objdups = []
		
		for obj in objects:
			if obj.type != 'MESH':
				continue
				
			bpy.ops.object.select_all(action='DESELECT')
			bpy.context.scene.objects.active = obj
			obj.select = True
			bpy.ops.object.duplicate()
			objdup = bpy.context.active_object
			objdups.append(objdup)
			object_name_lookup_orig[objdup] = obj.name
			
			if self.option_applymod:
				if not(objdup.data.shape_keys):
					while (len(objdup.modifiers)):
						bpy.ops.object.modifier_apply(apply_as='DATA', modifier = objdup.modifiers[0].name)

			# Options
			bpy.ops.object.mode_set( mode = 'EDIT' )
			if self.option_remove_doubles:
				bpy.ops.object.mode_set( mode = 'EDIT' )
				bpy.ops.mesh.select_all( action = 'SELECT' )
				bpy.ops.mesh.remove_doubles()
			if self.option_triangulate:
				bpy.ops.mesh.select_all( action = 'SELECT' )
				bpy.ops.mesh.quads_convert_to_tris()
			if self.option_normals:
				bpy.ops.object.mode_set( mode = 'EDIT' )
				bpy.ops.mesh.select_all( action = 'SELECT' )
				bpy.ops.mesh.normals_make_consistent()

			# Transformations
			bpy.ops.object.mode_set( mode = 'OBJECT' )
			bpy.ops.object.transform_apply( location = self.option_apply_location, rotation = self.option_apply_rotation, scale = self.option_apply_scale )

			mesh = objdup.data
			if mesh:
				mesh_object_name_lookup[mesh] = obj.name
				if not(self.option_batch):
					self.meshes.append(mesh)
					
					
		for obj in objdups:
			if (self.option_batch):
				self.meshes = [obj.data]

			if (self.option_batch):
				filename = os.path.dirname(filename)
				filename += (os.sep + object_name_lookup_orig[obj].replace('.', '_'))
			if not filename.lower().endswith('.lwo'):
				filename += '.lwo'
			file = open(filename, "wb")
		
			matmeshes, material_names = self.get_used_material_names()
			self.clips = []
			self.clippaths = []
			self.currclipid = 1
			tags = self.generate_tags(material_names)
			surfs = []
			chunks = [tags]
		
			meshdata = io.BytesIO()
			
			layer_index = 0
			
			for i, mesh in enumerate(self.meshes):
				if not(self.option_batch):
					mobj = objdups[i]
					
				if mesh.vertex_colors:
					#if meshtools.average_vcols:
					#	vmap_vc = generate_vmap_vc(mesh)  # per vert
					#else:
					if self.option_idtech:
						vmap_vcs = self.generate_vmap_vc(mesh)  # per vert
					else:
						vmad_vcs = self.generate_vmad_vc(mesh)  # per face
				
				for j, m in enumerate(matmeshes):
					if m == mesh:
						surfs.append(self.generate_surface(m, material_names[j]))
				layr = self.generate_layr(mesh_object_name_lookup[mesh], layer_index)
				pnts = self.generate_pnts(mesh)
				bbox = self.generate_bbox(mesh)
				if not(self.option_idtech):
					if not(self.option_normaddon and 'vertex_normal_list' in mobj):
						vnorms = self.generate_vnorms(mesh, None)
					else:
						vnorms = self.generate_vnorms(mesh, mobj.vertex_normal_list)
				pols = self.generate_pols(mesh, self.option_subd)
				if not(self.option_idtech):
					if not(self.option_normaddon and 'vertex_normal_list' in mobj):
						lnorms = self.generate_lnorms(mesh)
				ptag = self.generate_ptag(mesh, material_names)
		
				if mesh.uv_layers:
					vmad_uvs = self.generate_vmad_uv(mesh)  # per face
		
				if not(self.option_idtech):
					creases = False
					for edge in mesh.edges:
						if edge.crease > 0:
							creases = True
							vmad_ew = self.generate_vmad_ew(mesh)
							break
			
					if mesh.shape_keys:
						vmap_morphs = self.generate_vmap_morph(mesh)
			
					if len(mobj.vertex_groups):
						vmap_weights = self.generate_vmap_weight(mobj)
		
				self.write_chunk(meshdata, "LAYR", layr); chunks.append(layr)
				self.write_chunk(meshdata, "PNTS", pnts); chunks.append(pnts)
				self.write_chunk(meshdata, "BBOX", bbox); chunks.append(bbox)
				if not(self.option_idtech):
					self.write_chunk(meshdata, "VMAP", vnorms); chunks.append(vnorms)
				if mesh.vertex_colors:
					if self.option_idtech:
						for vmap in vmap_vcs:
							self.write_chunk(meshdata, "VMAP", vmap)
							chunks.append(vmap)
					else:
						for vmad in vmad_vcs:
							self.write_chunk(meshdata, "VMAD", vmad)
							chunks.append(vmad)
				self.write_chunk(meshdata, "POLS", pols); chunks.append(pols)
				if not(self.option_idtech):
					if not(self.option_normaddon and 'vertex_normal_list' in mobj):
						self.write_chunk(meshdata, "VMAD", lnorms); chunks.append(lnorms)
				self.write_chunk(meshdata, "PTAG", ptag); chunks.append(ptag)
		
				if mesh.uv_layers:
					for vmad in vmad_uvs:
						self.write_chunk(meshdata, "VMAD", vmad)
						chunks.append(vmad)
				
				if not(self.option_idtech):
					if creases:
						self.write_chunk(meshdata, "VMAD", vmad_ew)
						chunks.append(vmad_ew)
		
					if len(mobj.vertex_groups):
						for vmap in vmap_weights:
							self.write_chunk(meshdata, "VMAP", vmap)
							chunks.append(vmap)
			
					if mesh.shape_keys:
						for vmap in vmap_morphs:
							self.write_chunk(meshdata, "VMAP", vmap)
							chunks.append(vmap)
		
				layer_index += 1
				
			surfs = list(surfs)
			for clip in self.clips:
				chunks.append(clip)
			for surf in surfs:
				chunks.append(surf)
		
			self.write_header(file, chunks)
			self.write_chunk(file, "TAGS", tags)
			file.write(meshdata.getvalue()); meshdata.close()
			for clip in self.clips:
				self.write_chunk(file, "CLIP", clip)
			for surf in surfs:
				self.write_chunk(file, "SURF", surf)
		
			file.close()
			
			bpy.ops.object.select_all(action='DESELECT')
			bpy.context.scene.objects.active = obj
			obj.select = True
			bpy.ops.object.delete()
			
			if not(self.option_batch):
				# if not batch exporting, all meshes of objects are already saved
				break
		
		for obj in objects:
			obj.select = True
		bpy.context.scene.objects.active = actobj
		
		
	# =======================================
	# === Generate Null-Terminated String ===
	# =======================================
	def generate_nstring(self, string):
		if len(string)%2 == 0:	# even
			string += "\0\0"
		else:					# odd
			string += "\0"
		return string
	
	# ===============================
	# === Get Used Material Names ===
	# ===============================
	def get_used_material_names(self):
		matnames = []
		matmeshes = []
		for mesh in self.meshes:
			if mesh.materials:
				for material in mesh.materials:
					if material:
						matmeshes.append(mesh)
						matnames.append(material.name)
			elif mesh.vertex_colors:
				matmeshes.append(mesh)
				matnames.append(self.LWO_VCOLOR_MATERIAL)
			else:
				matmeshes.append(mesh)
				matnames.append(self.LWO_DEFAULT_MATERIAL)
		return matmeshes, matnames
	
	# =========================================
	# === Generate Tag Strings (TAGS Chunk) ===
	# =========================================
	def generate_tags(self, material_names):
		data = io.BytesIO()
		if material_names:
			for mat in material_names:
				data.write(bytes(self.generate_nstring(mat), 'UTF-8'))
			return data.getvalue()
		else:
			return self.generate_nstring('')
	
	# ========================
	# === Generate Surface ===
	# ========================
	def generate_surface(self, mesh, name):
		#if name.find("\251 Per-") == 0:
		#	return generate_vcol_surf(mesh)
		if name == self.DEFAULT_NAME:
			return self.generate_default_surf()
		else:
			return self.generate_surf(mesh, name)
	
	# ===================================
	# === Generate Layer (LAYR Chunk) ===
	# ===================================
	def generate_layr(self, name, idx):
		px, py, pz = bpy.data.objects.get(name).location
		data = io.BytesIO()
		data.write(struct.pack(">h", idx))			# layer number
		data.write(struct.pack(">h", 0))			# flags
		data.write(struct.pack(">fff", px, pz, py))	# pivot
		data.write(bytes(self.generate_nstring(name.replace(" ","_").replace(".", "_")), 'UTF-8'))
		return data.getvalue()
	
	# ===================================
	# === Generate Verts (PNTS Chunk) ===
	# ===================================
	def generate_pnts(self, mesh):
		data = io.BytesIO()
		for i, v in enumerate(mesh.vertices):
			x, y, z = v.co
			x *= self.option_scale
			y *= self.option_scale
			z *= self.option_scale
			data.write(struct.pack(">fff", x, z, y))
		return data.getvalue()
	
	# ============================================
	# === Generate Vertex Normals (VMAP Chunk) ===
	# ============================================
	def generate_vnorms(self, mesh, nolist):
		data = io.BytesIO()
		name = self.generate_nstring("vert_normals")
		data.write(b"NORM")										# type
		data.write(struct.pack(">H", 3))						# dimension
		data.write(bytes(name, 'UTF-8')) 						# name
		for i, v in enumerate(mesh.vertices):
			if nolist:
				x, y, z = nolist[i]['normal']
			else:
				x, y, z = v.normal
			x *= self.option_scale
			y *= self.option_scale
			z *= self.option_scale
			data.write(self.generate_vx(i)) # vertex index
			data.write(struct.pack(">fff", x, z, y))
		return data.getvalue()
	
	# ============================================
	# === Generate Loop Normals (VMAD Chunk) ===
	# ============================================
	def generate_lnorms(self, mesh):
		mesh.calc_normals_split()
		data = io.BytesIO()
		name = self.generate_nstring("vert_normals")
		data.write(b"NORM")										# type
		data.write(struct.pack(">H", 3))						# dimension
		data.write(bytes(name, 'UTF-8')) 						# name
		for i, p in enumerate(mesh.polygons):
			for li in p.loop_indices:
				l = mesh.loops[li]
				x, y, z = l.normal
				x *= self.option_scale
				y *= self.option_scale
				z *= self.option_scale
				data.write(self.generate_vx(l.vertex_index)) # vertex index
				data.write(self.generate_vx(i)) # face index
				data.write(struct.pack(">fff", x, z, y))
		return data.getvalue()
	
	# ==========================================
	# === Generate Bounding Box (BBOX Chunk) ===
	# ==========================================
	def generate_bbox(self, mesh):
		data = io.BytesIO()
		# need to transform verts here
		if mesh.vertices:
			nv = [v.co for v in mesh.vertices]
			xx = [ co[0] * self.option_scale for co in nv ]
			yy = [ co[1] * self.option_scale for co in nv ]
			zz = [ co[2] * self.option_scale for co in nv ]
		else:
			xx = yy = zz = [0.0,]
		
		data.write(struct.pack(">6f", min(xx), min(zz), min(yy), max(xx), max(zz), max(yy)))
		return data.getvalue()
	
	# ========================================
	# === Average All Vertex Colors (Fast) ===
	# ========================================
	'''
	def average_vertexcolors(self, mesh):
		vertexcolors = {}
		vcolor_add = lambda u, v: [u[0]+v[0], u[1]+v[1], u[2]+v[2], u[3]+v[3]]
		vcolor_div = lambda u, s: [u[0]/s, u[1]/s, u[2]/s, u[3]/s]
		for i, f in enumerate(mesh.faces):	# get all vcolors that share this vertex
			if not i%100:
				Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Finding Shared VColors")
			col = f.col
			for j in range(len(f)):
				index = f[j].index
				color = col[j]
				r,g,b = color.r, color.g, color.b
				vertexcolors.setdefault(index, []).append([r,g,b,255])
		i = 0
		for index, value in vertexcolors.iteritems():	# average them
			if not i%100:
				Blender.Window.DrawProgressBar(float(i)/len(mesh.verts), "Averaging Vertex Colors")
			vcolor = [0,0,0,0]	# rgba
			for v in value:
				vcolor = vcolor_add(vcolor, v)
			shared = len(value)
			value[:] = vcolor_div(vcolor, shared)
			i+=1
		return vertexcolors
	'''
	
	# ====================================================
	# === Generate Per-Vert Vertex Colors (VMAP Chunk) ===
	# ====================================================
	def generate_vmap_vc(self, mesh):
		alldata = []
		layers = mesh.vertex_colors
		for l in layers:
			vcname = self.generate_nstring(l.name)
			data = io.BytesIO()
			data.write(b"RGBA")										# type
			data.write(struct.pack(">H", 4))						# dimension
			data.write(bytes(vcname, 'UTF-8')) # name
			print(vcname)
			
			found = False
			doneverts = []
			for i, p in enumerate(mesh.polygons):
				p_vi = p.vertices
				for v, loop in zip(p.vertices, p.loop_indices):
					if v in doneverts:
						continue
					searchl = list(p.loop_indices)
					searchl.extend(list(p.loop_indices))
					pos = searchl.index(loop)
					prevc = l.data[searchl[pos - 1]].color
					nextc = l.data[searchl[pos + 1]].color
					vcol = l.data[loop].color
					if abs(prevc[0] - vcol[0]) < 0.1 and abs(nextc[0] - vcol[0]) < 0.1:
						continue
					doneverts.append(v)
					data.write(self.generate_vx(v)) # vertex index
					data.write(struct.pack(">ffff", vcol[0], vcol[1], vcol[2], 0.5))
					found = True
			if found:
				alldata.append(data.getvalue())
				
		return alldata
	
	# ====================================================
	# === Generate Per-Face Vertex Colors (VMAD Chunk) ===
	# ====================================================
	def generate_vmad_vc(self, mesh):
		alldata = []
		layers = mesh.vertex_colors
		for l in layers:
			vcname = self.generate_nstring(l.name)
			data = io.BytesIO()
			data.write(b"RGB ")										# type
			data.write(struct.pack(">H", 3))						# dimension
			data.write(bytes(vcname, 'UTF-8')) # name
			
			found = False
			for i, p in enumerate(mesh.polygons):
				p_vi = p.vertices
				for v, loop in zip(p.vertices, p.loop_indices):
					r,g,b = tuple(l.data[loop].color)
					data.write(self.generate_vx(v)) # vertex index
					data.write(self.generate_vx(i)) # face index
					data.write(struct.pack(">fff", r, g, )
					found = True
			if found:
				alldata.append(data.getvalue())
					
		return alldata
	
	# ================================================
	# === Generate Per-Face UV Coords (VMAD Chunk) ===
	# ================================================
	def generate_vmad_uv(self, mesh):
		alldata = []
		layers = mesh.uv_layers
		for l in layers:
			uvname = self.generate_nstring(l.name)
			data = io.BytesIO()
			data.write(b"TXUV")										 # type
			data.write(struct.pack(">H", 2))						 # dimension
			data.write(bytes(uvname, 'UTF-8')) # name
			
			found = False
			for i, p in enumerate(mesh.polygons):
				for v, loop in zip(p.vertices, p.loop_indices):
					searchl = list(p.loop_indices)
					searchl.extend(list(p.loop_indices))
					pos = searchl.index(loop)
					prevl = searchl[pos - 1]
					nextl = searchl[pos + 1]
					youv = l.data[loop].uv
					if l.data[prevl].uv == youv == l.data[nextl].uv:
						continue
					data.write(self.generate_vx(v)) # vertex index
					data.write(self.generate_vx(i)) # face index
					data.write(struct.pack(">ff", youv[0], youv[1]))
					found = True
			if found:
				alldata.append(data.getvalue())
				
		return alldata
	
	# ================================================
	# === Generate Edge Weights (VMAD Chunk) ===
	# ================================================
	def generate_vmad_ew(self, mesh):
		data = io.BytesIO()
		data.write(b"WGHT")										 # type
		data.write(struct.pack(">H", 1))						 # dimension
		data.write(bytes(self.generate_nstring("Edge Weight"), 'UTF-8')) # name
		face_edge_map = {ek: mesh.edges[i] for i, ek in enumerate(mesh.edge_keys)}
		for i, p in enumerate(mesh.polygons):
			vs = list(p.vertices)
			for ek in p.edge_keys:
				edge = face_edge_map[ek]
				if edge.crease == 0:
					continue
				v1, v2 = edge.vertices
				if vs[vs.index(v1) - 1] == v2:
					vi = v1
				else:
					vi = v2
				data.write(self.generate_vx(vi)) # vertex index
				data.write(self.generate_vx(i)) # face index
				data.write(struct.pack(">f", edge.crease))
					
		return data.getvalue()
	
	# ================================================
	# === Generate Endomorphs (VMAP Chunk) ===
	# ================================================
	def generate_vmap_morph(self, mesh):
		alldata = []
		keyblocks = mesh.shape_keys.key_blocks
		for kb in keyblocks:
			emname = self.generate_nstring(kb.name)
			data = io.BytesIO()
			data.write(b"MORF")										 # type
			data.write(struct.pack(">H", 3))						 # dimension
			data.write(bytes(emname, 'UTF-8')) # name
			for i, v in enumerate(mesh.vertices):
				x, y, z = kb.data[v.index].co - v.co
				data.write(self.generate_vx(v.index)) # vertex index
				data.write(struct.pack(">fff", x, z, y))
			alldata.append(data.getvalue())
					
		return alldata
	
	# ================================================
	# === Generate Weightmap (VMAP Chunk) ===
	# ================================================
	def generate_vmap_weight(self, obj):
		alldata = []
		vgroups = obj.vertex_groups
		for vg in vgroups:
			vgname = self.generate_nstring(vg.name)
			data = io.BytesIO()
			data.write(b"WGHT")										 # type
			data.write(struct.pack(">H", 1))						 # dimension
			data.write(bytes(vgname, 'UTF-8')) # name
			for i, v in enumerate(obj.data.vertices):
				w = 0.0
				try:
					w = vg.weight(v.index)
				except:
					pass
				data.write(self.generate_vx(v.index)) # vertex index
				data.write(struct.pack(">f", w))
			alldata.append(data.getvalue())
					
		return alldata
	
	# ======================================
	# === Generate Variable-Length Index ===
	# ======================================
	def generate_vx(self, index):
		if index < 0xFF00:
			value = struct.pack(">H", index)				 # 2-byte index
		else:
			value = struct.pack(">L", index | 0xFF000000)	 # 4-byte index
		return value
	
	# ===================================
	# === Generate Faces (POLS Chunk) ===
	# ===================================
	def generate_pols(self, mesh, subd):
		data = io.BytesIO()
		if subd:
			data.write(b"SUBD") # subpatch polygon type
		else:
			data.write(b"FACE") # normal polygon type
		for i,p in enumerate(mesh.polygons):
			data.write(struct.pack(">H", len(p.vertices))) # numfaceverts
			numfaceverts = len(p.vertices)
			p_vi = p.vertices
			for j in range(numfaceverts-1, -1, -1):			# Reverse order
				data.write(self.generate_vx(p_vi[j]))
		bm = bmesh.new()
		bm.from_mesh(mesh)
		for e in bm.edges:
			if len(e.link_faces) == 0:
				data.write(struct.pack(">H", 2))
				data.write(self.generate_vx(e.verts[0].index))
				data.write(self.generate_vx(e.verts[1].index))		
		bm.to_mesh(mesh)
		
		return data.getvalue()
	
	# =================================================
	# === Generate Polygon Tag Mapping (PTAG Chunk) ===
	# =================================================
	def generate_ptag(self, mesh, material_names):
		data = io.BytesIO()
		data.write(b"SURF")
		for poly in mesh.polygons:
			if mesh.materials:
				matindex = poly.material_index
				matname = mesh.materials[matindex].name
				surfindex = material_names.index(matname)
				
				data.write(self.generate_vx(poly.index))
				data.write(struct.pack(">H", surfindex)) 
			else:
				data.write(self.generate_vx(poly.index))
				data.write(struct.pack(">H", 0)) 
		return data.getvalue()
	
	# ===================================================
	# === Generate VC Surface Definition (SURF Chunk) ===
	# ===================================================
	"""
	def generate_vcol_surf(mesh):
		data = io.BytesIO()
		if len(mesh.vertex_colors):
			surface_name = self.generate_nstring(self.VCOL_NAME)
		data.write(surface_name)
		data.write(b"\0\0")
	
		data.write(b"COLR")
		data.write(struct.pack(">H", 14))
		data.write(struct.pack(">fffH", 1, 1, 1, 0))
	
		data.write(b"DIFF")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", 0.0, 0))
	
		data.write(b"LUMI")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", 1.0, 0))
	
		data.write(b"VCOL")
		data.write(struct.pack(">H", 34))
		data.write(struct.pack(">fH4s", 1.0, 0, "RGB "))  # intensity, envelope, type
		data.write(bytes(map(ord, self.generate_nstring(mesh.vert_colors.active.name)))) # name
	
		data.write(b"CMNT") # material comment
		comment = "Vertex Colors: Exported from Blender\256 2.70"
		comment = self.generate_nstring(comment)
		data.write(struct.pack(">H", len(comment)))
		data.write(bytes(map(ord, comment)))
		return data.getvalue()
	"""
	
	# ================================================
	# === Generate Surface Definition (SURF Chunk) ===
	# ================================================
	def generate_surf(self, mesh, material_name):
		data = io.BytesIO()
		data.write(bytes(self.generate_nstring(material_name), 'UTF-8'))
		
		try:
			material = bpy.data.materials.get(material_name)
			R,G,B = material.diffuse_color[0], material.diffuse_color[1], material.diffuse_color[2]
			diff = material.diffuse_intensity
			lumi = material.emit
			spec = material.specular_intensity
			gloss = math.sqrt((material.specular_hardness - 4) / 400)
			if material.raytrace_mirror.use:
				refl = material.raytrace_mirror.reflect_factor
			else:
				refl = 0.0
			rblr = 1.0 - material.raytrace_mirror.gloss_factor
			rind = material.raytrace_transparency.ior
			tran = 1.0 - material.alpha
			tblr = 1.0 - material.raytrace_transparency.gloss_factor
			trnl = material.translucency
			
			
		except:
			material = None
			
			R=G=B = 1.0
			diff = 1.0
			lumi = 0.0
			spec = 0.2
			hard = 0.0
			gloss = 0.0
			refl = 0.0
			rblr = 0.0
			rind = 1.0
			tran = 0.0
			tblr = 0.0
			trnl = 0.0
			sman = 0.0
		
			
		data.write(b"COLR")
		data.write(struct.pack(">H", 0))
		
		data.write(b"COLR")
		data.write(struct.pack(">H", 14))
		data.write(struct.pack(">fffH", R, G, B, 0))
	
		data.write(b"DIFF")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", diff, 0))
	
		data.write(b"LUMI")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", lumi, 0))
	
		data.write(b"SPEC")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", spec, 0))
	
		if not(self.option_idtech):
			data.write(b"REFL")
			data.write(struct.pack(">H", 6))
			data.write(struct.pack(">fH", refl, 0))
		
			data.write(b"RBLR")
			data.write(struct.pack(">H", 6))
			data.write(struct.pack(">fH", rblr, 0))
		
			data.write(b"TRAN")
			data.write(struct.pack(">H", 6))
			data.write(struct.pack(">fH", tran, 0))
		
			data.write(b"RIND")
			data.write(struct.pack(">H", 6))
			data.write(struct.pack(">fH", rind, 0))
			
			data.write(b"TBLR")
			data.write(struct.pack(">H", 6))
			data.write(struct.pack(">fH", tblr, 0))
			
			data.write(b"TRNL")
			data.write(struct.pack(">H", 6))
			data.write(struct.pack(">fH", trnl, 0))
		
		data.write(b"GLOS")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", gloss, 0))
	
		if material:
			vcname = material.vcmenu
			if vcname != "<none>":
				data.write(b"VCOL")
				data_tmp = io.BytesIO()
				data_tmp.write(struct.pack(">fH4s", 1.0, 0, b"RGBA"))  # intensity, envelope, type
				data_tmp.write(bytes(self.generate_nstring(vcname), 'UTF-8')) # name
				data.write(struct.pack(">H", len(data_tmp.getvalue())))
				data.write(data_tmp.getvalue())
	
		data.write(b"SMAN")
		data.write(struct.pack(">H", 4))
		if self.option_idtech:
			data.write(struct.pack(">f", 1.5707964))
		elif self.option_smooth:
			data.write(struct.pack(">f", 1.5))
		else:
			data.write(struct.pack(">f", 0))
	
#		data.write(b"SIDE")
#		data.write(struct.pack(">H", 2))
#		data.write(struct.pack(">H", 3))
	
		if not(self.option_idtech):
			# Check if the material contains any image maps
			def make_ord(nbloks, index):
				i = 8
				d = 16
				while i < 128:
					if i >= nbloks:
						break;
					d /= 2
					i *= 2
				ordinal  = int(128 + index * d)
				return ordinal
	
			if material:
				mtextures = list(material.texture_slots)	# Get a list of textures linked to the material
				for mtex in mtextures:
					if mtex:
						tex = mtex.texture
						if (tex.type == 'IMAGE'):	# Check if the texture is of type "IMAGE"
							path = tex.image.filepath
							if path in self.clippaths:
								clipid = self.clippaths.index(path)
							else:
								self.clippaths.append(path)
								clipid = self.currclipid
								self.clips.append(self.generate_clip(path))
								
							def write_tex_blok(data, channel, opac):
								data.write(b"BLOK")		# Surface BLOK header
				
								# IMAP subchunk (image map sub header)
								data_blok = io.BytesIO()
								data_blok.write(b"IMAP")					
								data_tmp = io.BytesIO()
								data_tmp.write(struct.pack(">B", make_ord(len(mtextures), clipid)))  # ordinal string
								data_tmp.write(struct.pack(">B", 0))
								data_tmp.write(b"CHAN")
								data_tmp.write(struct.pack(">H", 4))
								data_tmp.write(bytes(channel, 'UTF-8'))
								opactype = 0
								if mtex.blend_type == 'SUBTRACT':
									opactype = 1
								elif mtex.blend_type == 'DIFFERENCE':
									opactype = 2
								elif mtex.blend_type == 'MULTIPLY':
									opactype = 3
								elif mtex.blend_type == 'DIVIDE':
									opactype = 4
								elif mtex.blend_type == 'ADD':
									opactype = 7
								data_tmp.write(b"OPAC")				  # Hardcoded texture layer opacity
								data_tmp.write(struct.pack(">H", 8))
								data_tmp.write(struct.pack(">H", opactype))
								data_tmp.write(struct.pack(">f", opac))
								data_tmp.write(struct.pack(">H", 0))
								data_tmp.write(b"ENAB")
								data_tmp.write(struct.pack(">HH", 2, 1))  # 1 = texture layer enabled
								nega = mtex.invert
								data_tmp.write(b"NEGA")
								data_tmp.write(struct.pack(">HH", 2, nega))  # Disable negative image (1 = invert RGB values)
								data_tmp.write(b"AXIS")
								data_tmp.write(struct.pack(">HH", 2, 1))
								data_blok.write(struct.pack(">H", len(data_tmp.getvalue())))
								data_blok.write(data_tmp.getvalue())
				
								# IMAG subchunk
								data_blok.write(b"IMAG")
								data_blok.write(struct.pack(">HH", 2, clipid))
								data_blok.write(b"PROJ")
								data_blok.write(struct.pack(">HH", 2, 5)) # UV projection
				
								data_blok.write(b"VMAP")
								uvname = self.generate_nstring(mtex.uv_layer)
								data_blok.write(struct.pack(">H", len(uvname)))
								data_blok.write(bytes(uvname, 'UTF-8'))
			
								data.write(struct.pack(">H", len(data_blok.getvalue())))
								data.write(data_blok.getvalue())
								
								return data
			
							if mtex.use_map_color_diffuse:
								opac = mtex.diffuse_color_factor
								write_tex_blok(data, "COLR", opac)
								
							if mtex.use_map_diffuse:
								opac = mtex.diffuse_factor
								write_tex_blok(data, "DIFF", opac)
								
							if mtex.use_map_emit:
								opac = mtex.emit_factor
								write_tex_blok(data, "LUMI", opac)
								
							if mtex.use_map_specular:
								opac = mtex.specular_factor
								write_tex_blok(data, "SPEC", opac)
								
							if mtex.use_map_hardness:
								opac = mtex.hardness_factor
								write_tex_blok(data, "GLOS", opac)
								
							if mtex.use_map_raymir:
								opac = mtex.raymir_factor
								write_tex_blok(data, "REFL", opac)
								
							if mtex.use_map_alpha:
								opac = mtex.alpha_factor
								write_tex_blok(data, "TRAN", opac)
								
							if mtex.use_map_translucency:
								opac = mtex.translucency_factor
								write_tex_blok(data, "TRNL", opac)
								
	#						if mtex.use_map_normal:
	#							opac = mtex.normal_factor
	#							write_tex_blok(data, "BUMP", opac)
				
		return data.getvalue()
	
	# =============================================
	# === Generate Default Surface (SURF Chunk) ===
	# =============================================
	def generate_default_surf(self):
		data = io.BytesIO()
		material_name = self.DEFAULT_NAME
		data.write(bytes(self.generate_nstring(material_name), 'UTF-8'))
	
		data.write(b"COLR")
		data.write(struct.pack(">H", 14))
		data.write(struct.pack(">fffH", 0.9, 0.9, 0.9, 0))
	
		data.write(b"DIFF")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", 0.8, 0))
	
		data.write(b"LUMI")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", 0, 0))
	
		data.write(b"SPEC")
		data.write(struct.pack(">H", 6))
		data.write(struct.pack(">fH", 0.4, 0))
	
		data.write(b"GLOS")
		data.write(struct.pack(">H", 6))
		gloss = 50 / (255/2.0)
		gloss = round(gloss, 1)
		data.write(struct.pack(">fH", gloss, 0))
	
		return data.getvalue()
	
	# ==================================================
	# === Generate Thumbnail Icon Image (ICON Chunk) ===
	# ==================================================
	"""
	def generate_icon(self):
		data = io.BytesIO()
		file = open("f:/obj/radiosity/lwo2_icon.tga", "rb") # 60x60 uncompressed TGA
		file.read(18)
		icon_data = file.read(3600) # ?
		file.close()
		data.write(struct.pack(">HH", 0, 60))
		data.write(icon_data)
		#print len(icon_data)
		return data.getvalue()
	"""
	
	# ===============================================
	# === Generate CLIP chunk with STIL subchunks ===
	# ===============================================
	def generate_clip(self, pathname):
		data = io.BytesIO()
		pathname = pathname[0:2] + pathname.replace("\\", "/")[2:]	# Convert to Modo standard path
		imagename = self.generate_nstring(pathname)
		data.write(struct.pack(">L", self.currclipid))						# CLIP sequence/id
		data.write(b"STIL")											# STIL image
		data.write(struct.pack(">H", len(imagename)))				# Size of image name
		data.write(bytes(imagename, 'UTF-8'))
		self.currclipid += 1
		return data.getvalue()
	
	# ===================
	# === Write Chunk ===
	# ===================
	def write_chunk(self, file, name, data):
		file.write(bytes(name, 'UTF-8'))
		file.write(struct.pack(">L", len(data)))
		file.write(data)
	
	# =============================
	# === Write LWO File Header ===
	# =============================
	def write_header(self, file, chunks):
		chunk_sizes = map(len, chunks)
		chunk_sizes = functools.reduce(operator.add, chunk_sizes)
		form_size = chunk_sizes + len(chunks)*8 + len("FORM")
		file.write(b"FORM")
		file.write(struct.pack(">L", form_size))
		file.write(b"LWO2")
	

def menu_func(self, context):
	self.layout.operator(LwoExport.bl_idname, text="Lightwave (.lwo)")

def register():
	bpy.app.handlers.scene_update_post.append(sceneupdate_handler)

	bpy.utils.register_module(__name__)

	bpy.types.INFO_MT_file_export.append(menu_func)

def unregister():
	bpy.app.handlers.scene_update_post.remove(sceneupdate_handler)

	bpy.utils.unregister_module(__name__)

	bpy.types.INFO_MT_file_export.remove(menu_func)

if __name__ == "__main__":
  register()
  
  
  
@persistent
def sceneupdate_handler(dummy):

	ob = bpy.context.active_object
	if ob:
		if ob.type == 'MESH':
			mesh = bpy.context.active_object.data
		
			itemlist = [("<none>", "<none>", "<none>")]
			vcs = mesh.vertex_colors
			for vc in vcs:
				itemlist.append((vc.name, vc.name, "Vertex Color Map"))
			bpy.types.Material.vcmenu = EnumProperty(
					items = itemlist,
					name = "Vertex Color Map",
					description = "LWO export: vertex color map for this material")

	return {'RUNNING_MODAL'}

  


 

 

 

It has an 'id Tech compatible' export option.

  • Like 2

Some things I'm repeatedly thinking about...

 

- louder scream when you're dying

Link to comment
Share on other sites

Cool, you got it working. If by any chance you need it, there's an Assimp viewer program that Serpentine was working on that does export to ASE. I use it all the time, specially because then I dont need to worry about my blender's version.

 

https://www.sendspace.com/file/l3ebz9

 

You will also need direct june 2010 redist.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recent Status Updates

    • Petike the Taffer  »  DeTeEff

      I've updated the articles for your FMs and your author category at the wiki. Your newer nickname (DeTeEff) now comes first, and the one in parentheses is your older nickname (Fieldmedic). Just to avoid confusing people who played your FMs years ago and remember your older nickname. I've added a wiki article for your latest FM, Who Watches the Watcher?, as part of my current updating efforts. Unless I overlooked something, you have five different FMs so far.
      · 0 replies
    • Petike the Taffer

      I've finally managed to log in to The Dark Mod Wiki. I'm back in the saddle and before the holidays start in full, I'll be adding a few new FM articles and doing other updates. Written in Stone is already done.
      · 4 replies
    • nbohr1more

      TDM 15th Anniversary Contest is now active! Please declare your participation: https://forums.thedarkmod.com/index.php?/topic/22413-the-dark-mod-15th-anniversary-contest-entry-thread/
       
      · 0 replies
    • JackFarmer

      @TheUnbeholden
      You cannot receive PMs. Could you please be so kind and check your mailbox if it is full (or maybe you switched off the function)?
      · 1 reply
    • OrbWeaver

      I like the new frob highlight but it would nice if it was less "flickery" while moving over objects (especially barred metal doors).
      · 4 replies
×
×
  • Create New...