blend2map
by Jason Howard · 09/17/2006 (8:58 pm) · 27 comments
Exporting blender geometry to Quark maps:
First of all, I must preface this by stating the obvious. Blender was not made for brush modeling -- Quark was. I'm not a complete idiot. So I'll ignore you if you rant about blender here...
Secondly, I love blender. So I'll ignore you if you want to rant about blender here...
Ok... If you're like me and you want to do movie sequences for your game in blender, you're not excited about re-modeling your Quark buildings and structures in blender. I read a little about some people who experimented with blender to map exporters, but I couldn't find their scripts anywhere. Am I looking in the wrong places, or are they no longer published?
Anyway - I decided to go ahead and make my own blender to Quark map exporter, knowing that blender wasn't meant for that kind of thing but wanting to do it anyway.
There are some ground rules. To export properly each you need follow these guidelines:
- Model using brushes only (convex polygons -- each as a seperate mesh object). For example, a cube is suitable completely-enclosed convex polygon. Modeling out of bricks or cubes would work.
- Dimples or any other sort of concave shapes are not allowed, but you can break them up into several smaller convex shapes. Perhaps future versions will support automatically splitting up concave shapes, but don't expect that anytime soon... that's very complex.
- The early versions of this script really only export the geometry. Exporting UV textures and lamps is a natural next step for development. It's complex, but not as difficult as breaking up concave geometry into convex shapes. For now, retexture inside Quark. Currently it just uses a default texture called "concrete". You'll need import a concrete.jpg to your Quark setup so that this will work.
- Use quark and the map2dif tools to export all the way into Torque.
- Expect this to have a lot of issues. It's in the early stages of development, and I don't have a lot of time. Plenty of interest, but not a lot of time...
If anyone wants to help, feel free -- but please share your updates to the script. If anyone wants to complain, I'd love to listen but I don't have time. I'm busy making things happen and suggest that you do the same. If anyone is glad to see this script posted, I'll be glad to make time to listen to that. For those who have been longing for a script like this -- here's my little contribution back to a GREAT Garage Games community. Thanks for everything.
Here goes nothing... I hope this doesn't result in total chaos when it hits the community, but I want to share it anyway...
Oh... and lastly -- I will be a Constructor fan when it is published. Maybe after that comes out I'll start working on a map2blend import script to get Constructor based geometry into blender rendered movie scenes...
First of all, I must preface this by stating the obvious. Blender was not made for brush modeling -- Quark was. I'm not a complete idiot. So I'll ignore you if you rant about blender here...
Secondly, I love blender. So I'll ignore you if you want to rant about blender here...
Ok... If you're like me and you want to do movie sequences for your game in blender, you're not excited about re-modeling your Quark buildings and structures in blender. I read a little about some people who experimented with blender to map exporters, but I couldn't find their scripts anywhere. Am I looking in the wrong places, or are they no longer published?
Anyway - I decided to go ahead and make my own blender to Quark map exporter, knowing that blender wasn't meant for that kind of thing but wanting to do it anyway.
There are some ground rules. To export properly each you need follow these guidelines:
- Model using brushes only (convex polygons -- each as a seperate mesh object). For example, a cube is suitable completely-enclosed convex polygon. Modeling out of bricks or cubes would work.
- Dimples or any other sort of concave shapes are not allowed, but you can break them up into several smaller convex shapes. Perhaps future versions will support automatically splitting up concave shapes, but don't expect that anytime soon... that's very complex.
- The early versions of this script really only export the geometry. Exporting UV textures and lamps is a natural next step for development. It's complex, but not as difficult as breaking up concave geometry into convex shapes. For now, retexture inside Quark. Currently it just uses a default texture called "concrete". You'll need import a concrete.jpg to your Quark setup so that this will work.
- Use quark and the map2dif tools to export all the way into Torque.
- Expect this to have a lot of issues. It's in the early stages of development, and I don't have a lot of time. Plenty of interest, but not a lot of time...
If anyone wants to help, feel free -- but please share your updates to the script. If anyone wants to complain, I'd love to listen but I don't have time. I'm busy making things happen and suggest that you do the same. If anyone is glad to see this script posted, I'll be glad to make time to listen to that. For those who have been longing for a script like this -- here's my little contribution back to a GREAT Garage Games community. Thanks for everything.
Here goes nothing... I hope this doesn't result in total chaos when it hits the community, but I want to share it anyway...
Oh... and lastly -- I will be a Constructor fan when it is published. Maybe after that comes out I'll start working on a map2blend import script to get Constructor based geometry into blender rendered movie scenes...
About the author
#2
09/17/2006 (9:00 pm)
So... you can use the code above, save it in a file called "blend2map_export.py", and copy it to your blender scripts subdirectory. Have fun.
#3
09/17/2006 (9:07 pm)
Would it be worth making a formal resource out of this? Maybe I'll go that route after the intial bugs are worked out of it...
#5
Thanks! Most of the links are broken in the historical attempts... but draekko.atspace.com/ has the python script from Benoit Touchette. His script is probably better, as this is my first attempt at programming Python...
By the way - your work on the dts exporter is just incredible... legendary even. Thanks.
09/18/2006 (3:43 am)
James,Thanks! Most of the links are broken in the historical attempts... but draekko.atspace.com/ has the python script from Benoit Touchette. His script is probably better, as this is my first attempt at programming Python...
By the way - your work on the dts exporter is just incredible... legendary even. Thanks.
#6
Map_Blender.py
Map_TxtConfig.py
I honestly don't know the condition of the code, but from what I recall, it worked fairly well. This subset of code also has the underpinnings of light features (spot, strobe, runway, etc) which could use some attention.
Have fun!
09/18/2006 (4:53 am)
Hi, Jason. I'm glad you're taking up this banner and hope you will have some success. I've played around with the map exporter quite a long time ago with Joe and James is correct: the scripts have not been messed with in a long, long time. If you'd like what I have, you might like the following:Map_Blender.py
Map_TxtConfig.py
I honestly don't know the condition of the code, but from what I recall, it worked fairly well. This subset of code also has the underpinnings of light features (spot, strobe, runway, etc) which could use some attention.
Have fun!
#7
www.garagegames.com/blogs/11127/10274
I did write a lightwave scene->MIS exporter type thingy here. so i've got a little experience with this sorta thing
09/18/2006 (12:16 pm)
Not this is handy as hell! Too bad i don't know blender :( Do you know where i can learn what everything in the .map means? I may write a lightwave expoter :)www.garagegames.com/blogs/11127/10274
I did write a lightwave scene->MIS exporter type thingy here. so i've got a little experience with this sorta thing
#8
I learned the basics of the map format from www.gamers.org/dEngine/quake/QDP/. There is a more updated version of that material at collective.valve-erc.com/index.php?go=map_format The links above from James also go into a lot of detail. Good luck.
09/18/2006 (3:36 pm)
@Ramen-sama. I like youre work.I learned the basics of the map format from www.gamers.org/dEngine/quake/QDP/. There is a more updated version of that material at collective.valve-erc.com/index.php?go=map_format The links above from James also go into a lot of detail. Good luck.
#9
09/18/2006 (3:38 pm)
@Scott & James: THANKS!!! I'll see what I can do to get all the historical progress bright people made into an updated version that works on blender v2.42. This community is so awesome.
#10
09/18/2006 (7:15 pm)
You should check out John Ratcliff's Code Suppository website - it has a neat convex decomposition library that could be useful to you.
#11
09/18/2006 (8:15 pm)
@Ben: Holy cow! That convex decomposition code will change my life! :) Neat stuff. ... just the exuse I needed to pull an all nighter -- who needs sleep anyway...
#12
09/19/2006 (3:20 am)
This looks fantastic! I'm really interested in tracking this progress -- I too am a big fan of Blender who is a little hesitant to switch everything over to learn Quark. Thanks loads!
#13
It would be really fun to add some simple map building tools (raising walls & adding floor and roof from a 2D floor-map, etc).
I've still got my eye on automatically reducing invalid brushes down to smaller valid ones. It seems possible, in the future.
09/23/2006 (5:49 pm)
I have some new code that I'll post this weekend. The work of my predecessors was magnificent. I'm working on a couple enhancements (after brushing up on matrices and 3d vectors) that should prevent some bugs that cause some exported faces to be uneditable in Quark... This is a fun project. It would be really fun to add some simple map building tools (raising walls & adding floor and roof from a 2D floor-map, etc).
I've still got my eye on automatically reducing invalid brushes down to smaller valid ones. It seems possible, in the future.
#14
How to Use the Code: Copy the code above into a text file called "blend2map_export.py" and place the file in the scripts subdirectory of your blender 2.42 installation. The script creates a new menu option in the File, Export menu for exporting Quark Army Knife maps. Give your map file a name, like "test.map", when you export. After you get it into map format, feel free to edit it in Quark (change and position textures, add lights and portals, etc). When it's ready, use map2dif to create your Torque building or structure.
Soon the texturing you do inside blender will export more precisely. I need to find a way to identify portals, and maybe even use seams for zones or something. I'll use the work of my predecessors to add lights to the code... Then this will be ready for prime time. In the meantime, have fun...
09/24/2006 (12:06 pm)
I posted the "2006.09.23" version of the code above. It should export geometry that is "editable" in Quark without producing overflow errors. It does a good job at setting up the defautl texture for square faces, but warps the texture for non-square faces. That will be corrected very soon -- I have the algorithm ready and I'm coding it now...How to Use the Code: Copy the code above into a text file called "blend2map_export.py" and place the file in the scripts subdirectory of your blender 2.42 installation. The script creates a new menu option in the File, Export menu for exporting Quark Army Knife maps. Give your map file a name, like "test.map", when you export. After you get it into map format, feel free to edit it in Quark (change and position textures, add lights and portals, etc). When it's ready, use map2dif to create your Torque building or structure.
Soon the texturing you do inside blender will export more precisely. I need to find a way to identify portals, and maybe even use seams for zones or something. I'll use the work of my predecessors to add lights to the code... Then this will be ready for prime time. In the meantime, have fun...
#15
The code below has some issues that I'm working out, but it gives you an idea where it's going. I intend to make both methods an option by the time this is done.
10/01/2006 (6:50 pm)
Here is another approach that should give the artist a lot more freedom. It takes every polygon and extrudes it inward (opposite the normal) to create brushes. That way you don't have to really worry about convex vs concave and stuff like that when designing your structure/building.The code below has some issues that I'm working out, but it gives you an idea where it's going. I intend to make both methods an option by the time this is done.
#!BPY
""" Registration info for Blender menus:
Name: 'Quake Army Knife (.map)...'
Blender: 242
Group: 'Export'
Top: 'Export to QuArK map file format.'
"""
__author__ = "Jason Howard"
__url__ = ("Author's site, http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11288")
__version__ = "2006.10.15"
__bpydoc__ = """\
This script creates a new export menu option (File, Export menu) option called Quark Army Knife (.map).
Select or create the export name for the new map file, then check the console to see if it worked.
This script attempts to export Blender meshes to Quake Army Knife's map format.
Users must be careful to make sure each mesh is concave, or it will not result
in a valid brush. Furthermore, each "brush" mesh should be a separate object.
Small or concave brushes cause issues. The default material is named concrete.
Script History:
2006.09.17 Posted initial attempt at this script on GarageGames.com
2006.09.18 Received tremendous GarageGames community support, including source code from the brilliant
work of legends like Joe Sulewski, Benoit Touchette, Scott Coursey, and James Urquhart.
Modernized some code to work with blender v2.42. Thanks to JOE SULEWSKI, BENOIT TOUCHETTE,
SCOTT COURSEY, JAMES URQUHART, and others for sharing thier projects with the community.
2006.09.23 Enhanced to prevent overflow errors that prevented some exported faces from being editable in Quark.
2006.09.24 Improved texture export quality (UV at right angle to each other) to make texture placement in Quark easier.
2006.10.01 Switched to new approach -- complete re-write. Extrude inward (away from normal) to convert each face to its
own brush. Will debug and make either approach an option.
2006.10.14 Finally got some more time (at the expense of sleep) to work on this... Made some improvements to the
face-to-polygon method, including auto-scaling, which fixed a lot of problems. Still some issues to work out
but really seeing some progress.
2006.10.15 It is now required that you convert your meshes to triangles before exporting to a Quark map with this script.
"""
# blend2map_export.py
# 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.
# .map face (plane) format notes:
# ( x1 y1 z1 ) ( x2 y2 z2 ) ( x3 y3 z3 ) texture_name [ tx1 ty1 tz1 toffs1 ] [ tx2 ty2 tz2 toffs2 ] rotation scaleX scaleY
# --- clockwise points on plane -------- texture FN--- vector&offset right----vector&offset "up"---- rotatED-- strech X & Y (neg = mirror; < 1 = squeeze)
# texture offsets are multiples of 16...
import Blender, meshtools
from Blender import Mesh, NMesh, Scene, Window, sys, Image, Draw, Mathutils
from Blender.Mathutils import *
import BPyMesh
import struct, os, cStringIO, time
def face_count(mesh):
i = 0;
for face in mesh.faces:
i = i + 1
return i
def vertex_count(face):
i=0;
for vert in face.v:
i = i + 1
return i
def getWorldCoordinates(v, obj):
vector = Vector(v)
vector.resize4D()
v = vector * obj.matrix
v.resize3D()
return v
def getFaceCenter(p1, p2, p3):
# return the average of x, y, and z for 3 points (a plane) and return it as the center point c
cx = (p1[0] + p2[0] + p3[0]) / 3.0
cy = (p1[1] + p2[1] + p3[1]) / 3.0
cz = (p1[2] + p2[2] + p3[2]) / 3.0
vC = Vector([cx, cy, cz])
return vC
def isConcave(mesh):
#check every edge for > 180 degrees...
concaveFlag = 0
for firstEdge in mesh.edges:
for secondEdge in mesh.edges:
if (firstEdge.v1 <> secondEdge.v1) and (firstEdge.v2 <> secondEdge.v2):
match = 0
if (firstEdge.v1 == secondEdge.v1):
match = 1
Vec1 = Vector(firstEdge.v1) - Vector(firstEdge.v2)
Vec2 = Vector(secondEdge.v1) - Vector(secondEdge.v2)
if (firstEdge.v2 == secondEdge.v1):
match = 1
Vec1 = Vector(firstEdge.v2) - Vector(firstEdge.v1)
Vec2 = Vector(secondEdge.v1) - Vector(secondEdge.v2)
if (firstEdge.v1 == secondEdge.v2):
match = 1
Vec1 = Vector(firstEdge.v1) - Vector(firstEdge.v2)
Vec2 = Vector(secondEdge.v2) - Vector(secondEdge.v1)
if (firstEdge.v2 == secondEdge.v2):
match = 1
Vec1 = Vector(firstEdge.v2) - Vector(firstEdge.v1)
Vec2 = Vector(secondEdge.v2) - Vector(secondEdge.v1)
if match == 1:
Vec1.normalize
Vec2.normalize
if AngleBetweenVecs(Vec1, Vec2) > 180:
concaveFlag = concaveFlag + 1
print("Large angle: %f\n" %AngleBetweenVecs(Vec1, Vec2))
return concaveFlag
def getBestScale():
# get min & max vertex coordinates for each axis
scn = Scene.GetCurrent()
objects = scn.getChildren()
maxV1 = 0
maxV2 = 0
maxV3 = 0
minV1 = 0
minV2 = 0
minV3 = 0
for obj in objects:
objType = obj.getType()
if objType == "Mesh":
mesh = obj.getData()
for face in mesh.faces:
for v in face.v:
wv = getWorldCoordinates(v, obj)
v1 = wv[0]
if v1 < minV1:
minV1 = v1
if v1 > maxV1:
maxV1 = v1
v2 = wv[1]
if v2 < minV2:
minV2 = v2
if v2 > maxV2:
maxV2 = v2
v3 = wv[2]
if v3 < minV3:
minV3 = v3
if v3 > maxV3:
maxV3 = v3
# get optimal scale for each axis (try to use as much of the available scale as possible)
v1Scale = 1.0
V1Span = maxV1 - minV1
if V1Span > 0:
v1Scale = 8000/V1Span
v2Scale = 1.0
V2Span = maxV2 - minV2
if V2Span > 0:
v2Scale = 8000/V2Span
v3Scale = 1.0
V3Span = maxV3 - minV3
if V3Span > 0:
v3Scale = 8000/V3Span
# choose the smalles of the optimal axis scales
Scale = v1Scale
if v2Scale < v1Scale:
Scale = v2Scale
if v3Scale < Scale:
Scale = v3Scale
print("ScaleFactor: %f %f (%f - %f = %f)\n" %(Scale, v1Scale, V1Span, maxV1, minV1))
print("ScaleFactor: %f %f (%f - %f = %f)\n" %(Scale, v2Scale, V2Span, maxV2, minV2))
print("ScaleFactor: %f %f (%f - %f = %f)\n" %(Scale, v3Scale, V3Span, maxV3, minV3))
#return it
return Scale
def exportMeshesAsBrushes(filename, EXPORT_SCALE=100.0):
file = open(filename,"wb")
file.write("// Originally exported by blend2map_export.py verion %s\n" %( __version__ ) )
file.write("// exported from blender v%s filename %s\n\n" %( Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\')[-1]) )
file.write("// Entity 0\n// worldspawn\n")
file.write("{\n")
file.write(" \"classname\" \"worldspawn\"\n")
file.write(" \"detail_number\" \"0\"\n")
file.write(" \"min_pixels\" \"250\"\n")
file.write(" \"geometry_scale\" \"32.0\"\n")
file.write(" \"light_geometry_scale\" \"32.0\"\n")
file.write(" \"ambient_color\" \"0 0 0\"\n")
file.write(" \"emergency_ambient_color\" \"0 0 0\"\n")
file.write(" \"mapversion\" \"220\"\n")
## make lists of applicable blender objects (from current scene)
Meshes = []
Lamps = []
scn = Scene.GetCurrent()
objects = scn.getChildren()
for obj in objects:
# # if the object can be a mesh
# mesh = BPyMesh.getMeshFromObject(obj, containerMesh, True, False, scn)
# if mesh:
# Meshes.append(obj.getName())
# else:
# if obj.getType() == "Lamp":
# Lamps.append(obj.getName())
objType = obj.getType()
if objType == "Mesh":
Meshes.append(obj.getName())
if objType == "Lamp":
Lamps.append(obj.getName())
# for each mesh brush
scaleFactor = getBestScale()
for meshName in Meshes:
obj = Blender.Object.Get(meshName)
mesh = obj.getData()
# concave-convex test
if isConcave(mesh):
print("Error Warning: Mesh %s is concave!!! Bad angles...\n" %obj.getName())
#export the mesh
print "Exporting mesh %s" %obj.name
file.write("// Brush based on blender object named %s with %d faces\n" %(obj.name, face_count(mesh)))
file.write(" {\n")
faceindex = 0
for face in mesh.faces:
# prepare face
vCount = vertex_count(face)
#scaleFactor = EXPORT_SCALE
v1 = getWorldCoordinates(face.v[0].co, obj) * scaleFactor
v2 = getWorldCoordinates(face.v[1].co, obj) * scaleFactor
v3 = getWorldCoordinates(face.v[2].co, obj) * scaleFactor
c1 = getFaceCenter(v1, v2, v3)
# Create a "standard size" triangle that includes c1 to define the plane the face is on
# translate triangle to c1 = origin (0,0,0)
translated_v1 = v1 - c1
translated_v2 = v2 - c1
translated_c1 = c1 - c1
# reset v1 and v2 lenths to 1
translated_v1.normalize
translated_v2.normalize
# scale v1 and v2 lengths
new_triangle_scale = 10.0
std_translated_v1 = translated_v1 * new_triangle_scale
std_translated_v2 = translated_v2 * new_triangle_scale
# "untranslate" triangle defining the face' plane
std_v1 = std_translated_v1 + c1
std_v2 = std_translated_v2 + c1
std_c1 = c1
# prepare default texture image name and UV; right angle between U and V... etc...
vTextureName = "concrete"
vU = translated_v1 #std or translated?
vV = translated_v2
vOffsetU = 0.0
vOffsetV = 0.0
vRotation = 0
vScaleU = 1.0
vScaleV = 1.0
#vV = vU * RotationMatrix(90, 3, "R", Vector(face.no))
vRotation = AngleBetweenVecs(vU, vV)
# write face data for this part of the brush
file.write(" ");
if len(face.v) >= 3:
# write 3 vertices to define the plane for this face of the brush (convex mesh)
file.write("( %f %f %f ) " %(std_c1[0], std_c1[1], std_c1[2] ))
file.write("( %f %f %f ) " %(std_v2[0], std_v2[1], std_v2[2] ))
file.write("( %f %f %f ) " %(std_v1[0], std_v1[1], std_v1[2] ))
# write texture data (image name, UV right and up directions, texture offsets (right and up), rotation, and scale (X&Y)
file.write("%s [ %f %f %f %f ] [ %f %f %f %f ] %d %f %f\n" %(vTextureName, vU[0], vU[1], vU[2], vOffsetU, vV[0], vV[1], vV[2], vOffsetV, vRotation, vScaleU, vScaleV))
# write comments about normals for debugging purposes
#vtri = TriangleNormal(Vector(v3[0], v3[1], v3[2]), Vector(v2[0], v2[1], v2[2]), Vector(v3[0], v3[1], v3[2]))
#file.write("//Brush Norm = (%f %f %f)\n" %(vtri[0], vtri[1], vtri[2]))
#vfnor = face.no
#file.write("// Face Norm = (%f %f %f)\n" %(vfnor[0], vfnor[1], vfnor[2]))
faceindex = faceindex + 1
if faceindex > 16:
print("Warning: mesh %s exceeds 16 faces! Invalid brush.\n" %obj.name)
file.write("//Warning: mesh exceeds 16 faces! Invalid brush.\n")
file.write(" }\n")
file.write("}\n")
# export lamps
# for each lamp in Lamps:
# ##########################
# insert lamp stuff...
# ##########################
file.flush()
file.close()
print "Done."
def exportPolygonsAsBrushes(filename, BRUSH__THICKNESS = 16.0, EXPORT_SCALE=0.0):
print("Opening file %s\n" %filename)
file = open(filename,"wb")
file.write("// Originally exported by blend2map_export.py verion %s\n" %( __version__ ) )
file.write("// exported from blender v%s filename %s\n\n" %( Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\')[-1]) )
file.write("// Entity 0\n// worldspawn\n")
file.write("{\n")
file.write(" \"classname\" \"worldspawn\"\n")
file.write(" \"detail_number\" \"0\"\n")
file.write(" \"min_pixels\" \"250\"\n")
file.write(" \"geometry_scale\" \"32.0\"\n")
file.write(" \"light_geometry_scale\" \"32.0\"\n")
file.write(" \"ambient_color\" \"0 0 0\"\n")
file.write(" \"emergency_ambient_color\" \"0 0 0\"\n")
file.write(" \"mapversion\" \"220\"\n")
#file.write("{\n")
## make lists of applicable blender objects (from current scene)
Meshes = []
Lamps = []
scn = Scene.GetCurrent()
objects = scn.getChildren()
for obj in objects:
objType = obj.getType()
if objType == "Mesh":
Meshes.append(obj.getName())
if objType == "Lamp":
Lamps.append(obj.getName())
# for each mesh brush
scaleFactor = EXPORT_SCALE
if scaleFactor == 0.0:
scaleFactor = getBestScale()
print("Best Scale Factor: %f\n" %scaleFactor)
for meshName in Meshes:
obj = Blender.Object.Get(meshName)
mesh = obj.getData()
# export the mesh
print "Exporting polygons in mesh %s" %obj.name
file.write("\n// Brushes based on polygons (faces) in blender object named %s with %d faces\n" %(obj.name, face_count(mesh)))
faceindex = 0
for face in mesh.faces:
file.write(" {\n")
# prepare face
vCount = vertex_count(face)
#face vertex list (vertexList)
vCount = vertex_count(face)
i = 0
vertexList = []
while i < vCount:
if i < 6:
vertexList.append(getWorldCoordinates(face.v[i].co, obj) * scaleFactor)
if i == 6:
print "Warning: Polygon exceeds 6 faces... only 6 were exported."
i = i + 1
#determine number of planes on this brush (2 more than sides on polygon -- because top & bottom will be added
if vCount > 6:
vCount = 6
countBrushPlanes = vCount + 2
if vCount < 3:
print "Error: Invaide brush! no sides"
#make a list of sides
i = 0
print("vCount = %d\n" %vCount)
sideListV1 = []
sideListV2 = []
while i < vCount:
b = i + 1
if b >= vCount:
b = 0
sideListV1.append( Vector(vertexList[i]) )
sideListV2.append( Vector(vertexList[b]) )
i = i + 1
#face normal
polyNormal = Vector(face.no)
#if vecLengthSquared(polyNormal) == 0.0:
# print "Invalid polygon: vecLengthSquared(polyNormal) = 0\n"
brushThickness = BRUSH__THICKNESS
thickness = Vector( polyNormal * -brushThickness ); #vecScale
print("polyNormal:")
print polyNormal
print ("thickness:")
print thickness
# ---- TOP PLANE ---- of brush (from polygon)
print "top..."
brushPlaneV3 = [Vector(vertexList[0])]
brushPlaneV2 = [Vector(vertexList[1])]
brushPlaneV1 = [Vector(vertexList[2])]
# future enhancement: make those points weren't on the same line; pick another point if they were
brushPlaneScale1 = [0.5]
brushPlaneScale2 = [0.5]
brushPlaneNormal = [polyNormal * -1]
#UV
U = [ Vector(brushPlaneV1[0][0] - brushPlaneV3[0][0], brushPlaneV1[0][1] - brushPlaneV3[0][1], brushPlaneV1[0][2] - brushPlaneV3[0][2]) ]
V = [ Vector(brushPlaneV2[0][0] - brushPlaneV3[0][0], brushPlaneV2[0][1] - brushPlaneV3[0][1], brushPlaneV2[0][2] - brushPlaneV3[0][2]) ]
offsetU = [ 0.0 ]
offsetV = [ 0.0 ]
#rotation = [ AngleBetweenVecs(U[0], V[0]) ]
rotation = [0]
scaleU = [ 1.0 ]
scaleV= [ 1.0 ]
brushPlaneTexture = ["concrete"]
# ---- BOTTOM PLANE ---- of brush (parallel to polygon; distance of thickness vector away from original poly)
print "bottom..."
# reverse order for opposite normal on "bottom" plane
v1 = brushPlaneV3[0] + thickness
v2 = brushPlaneV2[0] + thickness
v3 = brushPlaneV1[0] + thickness
# print brushPlaneV1[0]
# print thickness
# print v1
brushPlaneV1.append( Vector(v1[0], v1[1], v1[2]) )
brushPlaneV2.append( Vector(v2[0], v2[1], v2[2]) )
brushPlaneV3.append( Vector(v3[0], v3[1], v3[2]) )
# print brushPlaneV1
brushPlaneScale1.append( 0.5 )
brushPlaneScale2.append( 0.5 )
brushPlaneNormal.append( polyNormal * -1 )
brushPlaneTexture.append( "concrete" )
#UV
U.append( Vector(brushPlaneV1[1][0] - brushPlaneV3[1][0], brushPlaneV1[1][1] - brushPlaneV3[1][1], brushPlaneV1[1][2] - brushPlaneV3[1][2]) )
V.append( Vector(brushPlaneV2[1][0] - brushPlaneV3[1][0], brushPlaneV2[1][1] - brushPlaneV3[1][1], brushPlaneV2[1][2] - brushPlaneV3[1][2]) )
offsetU.append( 0.0 )
offsetV.append( 0.0 )
#rotation.append( AngleBetweenVecs(U[1], V[1]) )
rotation.append(0)
scaleU.append( 1.0 )
scaleV.append( 1.0 )
# ---- SIDE PLANES ---- (connect top to bottom)
i = 2
while i < countBrushPlanes:
print("side %d..." %i)
brushPlaneV2.append( sideListV2[i-2] )
brushPlaneV1.append( sideListV1[i-2] )
brushPlaneV3.append( sideListV1[i-2] + thickness ) # vecMA
brushPlaneScale1.append ( 0.5 )
brushPlaneScale2.append ( 0.5 )
# tv1 = Vector(sideList[i-2][0], sideList[i-2][1])
# tv2 = Vector(polyNormal[0])
# brushPlaneNormal.append( CrossVecs( tv1, tv2 ) ) #crossProduct of 2 vectors
# print "set"
brushPlaneTexture.append( "concrete" )
#UV
U.append( brushPlaneV1[i] - brushPlaneV3[i] )
V.append( brushPlaneV2[i] - brushPlaneV3[i] )
offsetU.append( 0.0 )
offsetV.append( 0.0 )
#rotation.append( AngleBetweenVecs(U[i], V[i]) )
rotation.append(0)
scaleU.append( 1.0 )
scaleV.append( 1.0 )
# print "ok"
i = i + 1
# write data for each plan on this brush
i = 0
while i < countBrushPlanes:
file.write(" ")
# write 3 vertices to define the plane for this face of the brush (convex mesh)
file.write("( %f %f %f ) " %(brushPlaneV1[i][0], brushPlaneV1[i][1], brushPlaneV1[i][2] ))
file.write("( %f %f %f ) " %(brushPlaneV2[i][0], brushPlaneV2[i][1], brushPlaneV2[i][2] ))
file.write("( %f %f %f ) " %(brushPlaneV3[i][0], brushPlaneV3[i][1], brushPlaneV3[i][2] ))
# write texture data (image name, UV right and up directions, texture offsets (right and up), rotation, and scale (X&Y)
file.write("%s [ %f %f %f %f ] [ %f %f %f %f ] %d %f %f\n" %(brushPlaneTexture[i], U[i][0], U[i][1], U[i][2], offsetU[i], V[i][0], V[i][1], V[i][2], offsetV[i], rotation[i], scaleU[i], scaleV[i]))
i = i + 1
file.write(" }\n")
# export lamps
# for each lamp in Lamps:
# ##########################
# insert lamp stuff...
# ##########################
file.write("}\n")
file.flush()
file.close()
print "Done."
def fs_callback(filename):
if filename.find('.map', -4) <= 0: filename += '.map'
print "---- blend2map export ----"
#exportMeshesAsBrushes(filename)
exportPolygonsAsBrushes(filename)
print" --------------------------"
Blender.Window.FileSelector(fs_callback, "Export MAP")
#16
10/13/2006 (11:43 pm)
Ok... I updated the code above. It still has some issues, but its danger-close to completion. I have successfully designed some buildings in blender, exported to Quark, exported to DIF, and used them in TGE's starter.fps. Some buildings failed -- I'm looking into the cases that fail. It's really progressing now.
#17
10/14/2006 (6:35 am)
Tip: It is necessary to convert your meshes to triangles before exporting to Quark -- otherwise this program will mess up the normals. I tried that and it worked with some fairly complex buildings. If you get leakage (gaps where faces should join) you can try increasing the brush thickness. I like 16... It is really working well now.
#18
10/14/2006 (7:14 am)
Well done Jason ! WhooHoo !
#19
10/14/2006 (9:34 am)
Ok... this was created in blender, exported to Quark, and exported to Torque as a DIF interior. It's an odd shape for a building, but blender enthusiasts will appreciate its significance. :)
#20
Quake .map exporter for Blender3D
blenderartists.org/forum/showthread.php?s=b9871f3327553ef81377b6061a0a474e&t=559...
members.iinet.net.au/%7Ecpbarton/export_map.py
I hope that this help you.
10/28/2006 (5:24 am)
Find some resource for you!Quake .map exporter for Blender3D
blenderartists.org/forum/showthread.php?s=b9871f3327553ef81377b6061a0a474e&t=559...
members.iinet.net.au/%7Ecpbarton/export_map.py
I hope that this help you.
Torque Owner Jason Howard
Default Studio Name
#!BPY """ Registration info for Blender menus: Name: 'Quake Army Knife (.map)...' Blender: 242 Group: 'Export' Top: 'Export to QuArK map file format.' """ __author__ = "Jason Howard" __url__ = ("Author's site, http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11288") __version__ = "2006.09.23" __bpydoc__ = """\ This script creates a new export menu option (File, Export menu) option called Quark Army Knife (.map). Select or create the export name for the new map file, then check the console to see if it worked. This script attempts to export Blender meshes to Quake Army Knife's map format. Users must be careful to make sure each mesh is concave, or it will not result in a valid brush. Furthermore, each "brush" mesh should be a separate object. Small or concave brushes cause issues. The default material is named concrete. Script History: 2006.09.17 Posted initial attempt at this script on GarageGames.com 2006.09.18 Received tremendous GarageGames community support, including source code from the brilliant work of legends like Joe Sulewski, Benoit Touchette, Scott Coursey, and James Urquhart. Modernized some code to work with blender v2.42. Thanks to JOE SULEWSKI, BENOIT TOUCHETTE, SCOTT COURSEY, and JAMES URQUHART for sharing thier projects with the community. 2006.09.23 After brushing up on 3D matrices math, enhanced to prevent errors that prevented some exported faces from being editable in Quark. """ # blend2map_export.py # 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. # .map face (plane) format notes: # ( x1 y1 z1 ) ( x2 y2 z2 ) ( x3 y3 z3 ) texture_name [ tx1 ty1 tz1 toffs1 ] [ tx2 ty2 tz2 toffs2 ] rotation scaleX scaleY # --- clockwise points on plane -------- texture FN--- vector&offset right----vector&offset "up"---- rotatED-- strech X & Y (neg = mirror; < 1 = squeeze) # texture offsets are multiples of 16... import Blender, meshtools from Blender import Mesh, NMesh, Scene, Window, sys, Image, Draw, Mathutils from Blender.Mathutils import * import BPyMesh import struct, os, cStringIO, time def face_count(mesh): i = 0; for face in mesh.faces: i = i + 1 return i def getWorldCoordinates(v, obj): vector = Vector(v) vector.resize4D() v = vector * obj.matrix v.resize3D() return v def getFaceCenter(p1, p2, p3): # return the average of x, y, and z for 3 points (a plane) and return it as the center point c cx = (p1[0] + p2[0] + p3[0]) / 3.0 cy = (p1[1] + p2[1] + p3[1]) / 3.0 cz = (p1[2] + p2[2] + p3[2]) / 3.0 vC = Vector([cx, cy, cz]) return vC def export(filename, EXPORT_SCALE=100.0): file = open(filename,"wb") file.write("// Originally exported by blend2map_export.py verion %s\n" %( __version__ ) ) file.write("// exported from blender v%s filename %s\n\n" %( Blender.Get('version'), Blender.Get('filename').split('/')[-1].split('\')[-1]) ) file.write("// Entity 0\n// worldspawn\n") file.write("{\n") file.write(" \"classname\" \"worldspawn\"\n") file.write(" \"detail_number\" \"0\"\n") file.write(" \"min_pixels\" \"250\"\n") file.write(" \"geometry_scale\" \"32.0\"\n") file.write(" \"light_geometry_scale\" \"32.0\"\n") file.write(" \"ambient_color\" \"0 0 0\"\n") file.write(" \"emergency_ambient_color\" \"0 0 0\"\n") file.write(" \"mapversion\" \"220\"\n") ## make lists of applicable blender objects (from current scene) Meshes = [] Lamps = [] scn = Scene.GetCurrent() objects = scn.getChildren() for obj in objects: # # if the object can be a mesh # mesh = BPyMesh.getMeshFromObject(obj, containerMesh, True, False, scn) # if mesh: # Meshes.append(obj.getName()) # else: # if obj.getType() == "Lamp": # Lamps.append(obj.getName()) objType = obj.getType() if objType == "Mesh": Meshes.append(obj.getName()) if objType == "Lamp": Lamps.append(obj.getName()) # for each mesh brush for meshName in Meshes: obj = Blender.Object.Get(meshName) mesh = obj.getData() #export the mesh print "Exporting mesh %s" %obj.name file.write("// Brush based on blender object named %s with %d faces\n" %(obj.name, face_count(mesh))) file.write(" {\n") faceindex = 0 for face in mesh.faces: # prepare face scaleFactor = EXPORT_SCALE v1 = getWorldCoordinates(face.v[0].co, obj) # do scale factor here instead? v2 = getWorldCoordinates(face.v[1].co, obj) v3 = getWorldCoordinates(face.v[2].co, obj) c1 = getFaceCenter(v1, v2, v3) # Create a smaller triangle that includes c1 to define the plane # translate triangle to c1 = origin (0,0,0) translated_v1 = v1 - c1 translated_v2 = v2 - c1 translated_c1 = c1 - c1 # reset v1 and v2 lenths to 1 translated_v1.normalize translated_v2.normalize # scale v1 and v2 lengths new_triangle_scale = 10.0 small_translated_v1 = translated_v1 * new_triangle_scale small_translated_v2 = translated_v2 * new_triangle_scale # "untranslate" triangle small_v1 = small_translated_v1 + c1 small_v2 = small_translated_v2 + c1 small_c1 = c1 # prepare UV and image print ".defaultUVs" vTextureName = "concrete" vU = translated_v1 vV = translated_v2 # project to create right angle... etc... scale properly... vOffsetU = 0.0 vOffsetV = 0.0 vRotation = 0 vScaleU = 1.0 vScaleV = 1.0 # write 3 vertices to define the plane for this face of the brush (convex mesh) file.write(" "); if len(face.v) >= 3: print ".write" # write vertexes file.write("( %f %f %f ) " %(small_c1[0] * scaleFactor, small_c1[1] * scaleFactor, small_c1[2] * scaleFactor )) file.write("( %f %f %f ) " %(small_v2[0] * scaleFactor, small_v2[1] * scaleFactor, small_v2[2] * scaleFactor )) file.write("( %f %f %f ) " %(small_v1[0] * scaleFactor, small_v1[1] * scaleFactor, small_v1[2] * scaleFactor )) # write texture data (image name, UV right and up directions, texture offsets (right and up), rotation, and scale (X&Y) file.write("%s [ %f %f %f %f ] [ %f %f %f %f ] %d %f %f\n" %(vTextureName, vU[0], vU[1], vU[2], vOffsetU, vV[0], vV[1], vV[2], vOffsetV, vRotation, vScaleU, vScaleV)) # write comments about normals for debugging purposes vtri = TriangleNormal(Vector(v3[0], v3[1], v3[2]), Vector(v2[0], v2[1], v2[2]), Vector(v3[0], v3[1], v3[2])) file.write("//Brush Norm = (%f %f %f)\n" %(vtri[0], vtri[1], vtri[2])) vfnor = face.no file.write("// Face Norm = (%f %f %f)\n" %(vfnor[0], vfnor[1], vfnor[2])) faceindex = faceindex + 1 if faceindex > 16: print("Warning: mesh %s exceeds 16 faces! Invalid brush.\n" %obj.name) file.write("//Warning: mesh exceeds 16 faces! Invalid brush.\n") file.write(" }\n") file.write("}\n") # export lamps # for each lamp in Lamps: # ########################## # insert lamp stuff... # ########################## file.flush() file.close() print "Done." def fs_callback(filename): if filename.find('.map', -4) <= 0: filename += '.map' print "---- blend2map export ----" export(filename, 10) print" --------------------------" Blender.Window.FileSelector(fs_callback, "Export MAP")