janexx Posted July 11, 2015 Report Posted July 11, 2015 Hi! I am new here. I want to help with modeling. I would like to model a statue. When it's ok, I would like to model something like this or this.I use Blender and have already some experience with modeling. If you are interested, here are my prvious working examples: myportfolio nice greetings 2 Quote
RPGista Posted July 11, 2015 Report Posted July 11, 2015 (edited) 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 July 11, 2015 by RPGista Quote
janexx Posted July 11, 2015 Author Report Posted July 11, 2015 Thank you ver much for your very kind feedback! I feel flattered. 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. 1 Quote
Gestas Posted July 11, 2015 Report Posted July 11, 2015 you have to unzip the contents to get the folders. Or you can look in model viewer in Dark Radiant and it will show the folders. Quote
Bikerdude Posted July 11, 2015 Report Posted July 11, 2015 Evening Janex, I imagine Springheel will be along at some point. But in the meantime what modeling software do you use, and more importatnly what model formates can you export/save to..? I ask because we like to use .LWO and .ASE Quote
RPGista Posted July 11, 2015 Report Posted July 11, 2015 (edited) 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 July 11, 2015 by RPGista Quote
SeriousToni Posted July 12, 2015 Report Posted July 12, 2015 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! 1 Quote "Einen giftigen Trank aus Kräutern und Wurzeln für die närrischen Städter wollen wir brauen." - Text aus einem verlassenen Heidenlager
janexx Posted July 12, 2015 Author Report Posted July 12, 2015 (edited) 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 July 12, 2015 by janexx 1 Quote
Gestas Posted July 12, 2015 Report Posted July 12, 2015 Dark Radiant is the editor, it is on the downloads page. You'll need it to test models in game anyway. Springheel is the TDM art guy. Quote
RPGista Posted July 12, 2015 Report Posted July 12, 2015 (edited) 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 July 12, 2015 by RPGista Quote
Springheel Posted July 12, 2015 Report Posted July 12, 2015 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. Quote TDM Missions: A Score to Settle * A Reputation to Uphold * A New Job * A Matter of Hours Video Series: Springheel's Modules * Speedbuild Challenge * New Mappers Workshop * Building Traps
janexx Posted July 13, 2015 Author Report Posted July 13, 2015 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? 1 Quote
Dragofer Posted July 13, 2015 Report Posted July 13, 2015 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. 1 Quote FM: One Step Too Far | FM: Down by the Riverside | FM: Perilous Refuge Co-FM: The Painter's Wife | Co-FM: Written in Stone | Co-FM: Seeking Lady Leicester Dragofer's Stuff | Dragofer's Scripting | A to Z Scripting Guide | Dark Ambient Music & Sound Repository
Popular Post janexx Posted October 31, 2015 Author Popular Post Report Posted October 31, 2015 Hi, My model, the statue is finished:http://wwws.htwk-leipzig.de/~jweigel/Unterseiten/Portfolio.html#slide1 It has 9091 faces. Any annotations/criticism is welcome. Now I would like to check the grid size in DarkRadiant. But I can't compile DR. See my post here. 5 Quote
Springheel Posted October 31, 2015 Report Posted October 31, 2015 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. Quote TDM Missions: A Score to Settle * A Reputation to Uphold * A New Job * A Matter of Hours Video Series: Springheel's Modules * Speedbuild Challenge * New Mappers Workshop * Building Traps
RPGista Posted October 31, 2015 Report Posted October 31, 2015 Absolutely amazing! Love the face, the body proportions... Feels very natural, but still like something sculpted. Quote
janexx Posted October 31, 2015 Author Report Posted October 31, 2015 (edited) 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: 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 October 31, 2015 by janexx 3 Quote
Bikerdude Posted October 31, 2015 Report Posted October 31, 2015 @Jane, Springheels 3D file format of choice is .LWO, but if you can't export to that then we can use .ASE. Quote
Springheel Posted October 31, 2015 Report Posted October 31, 2015 Pm me and I'll send you the particulars. Quote TDM Missions: A Score to Settle * A Reputation to Uphold * A New Job * A Matter of Hours Video Series: Springheel's Modules * Speedbuild Challenge * New Mappers Workshop * Building Traps
janexx Posted November 8, 2015 Author Report Posted November 8, 2015 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. 1 Quote
VanishedOne Posted November 8, 2015 Report Posted November 8, 2015 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. 2 Quote Some things I'm repeatedly thinking about... - louder scream when you're dying
RPGista Posted November 9, 2015 Report Posted November 9, 2015 I can do it for you! I will also upload later today a program I use to convert to ase, so you only need to export an obj from blender. Quote
janexx Posted November 9, 2015 Author Report Posted November 9, 2015 @VanishedOne Thanks for the script! It seems to work! RPGista, if you want, you can send me your program nonetheless. Thanks for your commitment. 1 Quote
RPGista Posted November 10, 2015 Report Posted November 10, 2015 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. Quote
RPGista Posted November 10, 2015 Report Posted November 10, 2015 PS: this is the version that works. For some reason, I cant get the last version to work for me. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.