add plugin ac3d import/export ac3d blender 2.6

git-svn-id: https://svn.code.sf.net/p/speed-dreams/code/trunk@5661 30fe4595-0a0c-4342-8851-515496e4dcbd

Former-commit-id: 609d63390db22096c7a621e3169866b300d85361
Former-commit-id: 050b10d4a55fe2053716725c3061c921ae94cc68
This commit is contained in:
torcs-ng 2013-08-06 20:07:37 +00:00
parent 2a659bb5a1
commit 718454b5b0
4 changed files with 1548 additions and 0 deletions

View file

@ -0,0 +1,402 @@
import bpy, os, shutil
from math import radians, degrees
from mathutils import Vector, Matrix
DEBUG = False
def TRACE(message):
if DEBUG:
print(message)
# ------------------------------------------------------------------------------
class Object:
'''
Base class for an AC3D object.
'''
def __init__( self,
name,
ob_type,
bl_obj,
export_config,
local_transform ):
'''
Create a AC3D object from a blender object and it's children
@param name The name of the object
@param ob_type The type of the object (world, poly, group)
@param bl_obj The according blender object
@param export_config Settings for export TODO move to export method?
'''
self.export_config = export_config
self.name = name.replace('"','') # quotes not allowed...
self.type = ob_type
self.bl_obj = bl_obj
self.data = '' # custom data (eg. description)
self.url = '' # custom url (use for whatever you want but never ever
# put spaces into it)
if bl_obj:
self.matrix_world = local_transform * bl_obj.matrix_world
self.pos_abs = self.matrix_world.to_translation()
else:
self.matrix_world = local_transform
self.pos_abs = None
self.children = []
self.parent = None
def addChild( self,
child ):
if not isinstance(child, Object):
raise Exception('addChild: can only add childs derived from Object')
child.parent = self
self.children.append(child)
def _parse( self, ac_mats, str_pre ):
'''
Override to process the blender mesh and add materials to ac_mats if
needed
'''
pass
def parse( self, ac_mats, str_pre = '' ):
TRACE("{0}+-({1}) {2}".format(str_pre, self.type, self.name))
self._parse(ac_mats, str_pre)
for child in self.children:
child.parse(ac_mats, str_pre + ' ')
def _write( self, strm ):
pass
def write( self, strm ):
strm.write('OBJECT {0}\nname "{1}"\n'.format(self.type, self.name))
if len(self.data):
strm.write('data {0}\n'.format(len(self.data)))
strm.write('{0}\n'.format(self.data))
if len(self.url):
strm.write('url {0}\n'.format(self.url))
if self.parent and self.pos_abs:
# position relative to parent
pos_rel = self.pos_abs - self.parent.matrix_world.to_translation()
location = self.export_config.global_matrix * pos_rel
strm.write('loc {0:.7f} {1:.7f} {2:.7f}\n'.format(location[0], location[1], location[2]))
self._write(strm)
strm.write('kids {0}\n'.format(len(self.children)))
for child in self.children:
child.write(strm)
# ------------------------------------------------------------------------------
class World (Object):
'''
Normally the root element is a world object
'''
def __init__( self,
name,
export_config,
local_transform = Matrix() ):
Object.__init__(self, name, 'world', None, export_config, local_transform)
# ------------------------------------------------------------------------------
class Poly (Object):
'''
A polygon mesh
'''
def __init__( self,
name,
bl_obj,
export_config,
local_transform = Matrix() ):
Object.__init__(self, name, 'poly', bl_obj, export_config, local_transform)
self.crease = None
self.vertices = []
self.surfaces = []
self.tex_name = '' # texture name (filename of texture)
self.tex_rep = [1,1] # texture repeat
self.ac_mats = {} # Blender to AC3d index cross-reference
def _parse( self, ac_mats, str_pre ):
if self.bl_obj:
TRACE('{0} ~ ({1}) {2}'.format( str_pre,
self.bl_obj.type,
self.bl_obj.data.name ))
#if self.bl_obj.type == 'MESH':
self._parseMesh(ac_mats)
def _parseMesh( self, ac_mats ):
mesh = self.bl_obj.to_mesh(self.export_config.context.scene, True, 'PREVIEW')
self._parseMaterials(mesh, ac_mats)
self._parseVertices(mesh)
self._parseFaces(mesh)
for mod in self.bl_obj.modifiers:
if mod.type=='EDGE_SPLIT':
self.crease = degrees(mod.split_angle)
break
if not self.crease:
if mesh.use_auto_smooth:
self.crease = degrees(mesh.auto_smooth_angle)
else:
self.crease = self.export_config.crease_angle
#bpy.data.meshes.remove(mesh)
def _parseMaterials( self, mesh, ac_mats ):
'''
Extract the materials from a blender mesh and create an id mapping from
object material index to global AC3D material index
'''
mat_index = 0 # local material index
for bl_mat in mesh.materials:
if not bl_mat:
continue
ac_mat = Material(bl_mat.name, bl_mat, self.export_config)
mat_exists = False
for mat in ac_mats:
if mat.same_as(ac_mat):
ac_mat = mat
mat_exists = True
break
if not mat_exists:
ac_mats.append(ac_mat)
if not len(self.tex_name):
for tex_slot in bl_mat.texture_slots:
if tex_slot and tex_slot.texture_coords == 'UV':
bl_tex = tex_slot.texture
bl_im = bl_tex.image
tex_name = bpy.path.basename(bl_im.filepath)
export_tex = os.path.join(self.export_config.exportdir, tex_name)
# TRACE('Exporting texture "{0}" to "{1}"'.format(bl_im.filepath, export_tex))
# TODO: Optionally over-write existing textures
if not os.path.exists(export_tex):
if bl_im.packed_file:
bl_im.file_format = 'PNG'
bl_im.filepath = export_tex
bl_im.unpack('WRITE_LOCAL')
else:
abs_path = bpy.path.abspath(bl_im.filepath)
if not os.path.exists(abs_path):
TRACE('Warning: Texture doesn\'t exists: {0}'.format(bl_im.filepath))
else:
shutil.copy(abs_path, export_tex)
# else:
# TRACE('File already exists "{0}"- not overwriting!'.format(tex_name))
self.tex_name = tex_name
break
# Blender to AC3d index cross-reference
# TRACE('Created Material {0} at index {1}'.format(ac_mats.index(ac_mat), mat_index))
self.ac_mats[mat_index] = ac_mats.index(ac_mat)
mat_index = mat_index + 1
def _parseVertices( self, mesh ):
'''
Extract the vertices from a blender mesh
'''
transform = self.export_config.global_matrix\
* Matrix.Translation(-self.pos_abs)\
* self.matrix_world
self.vertices = [transform * v.co for v in mesh.vertices]
def _parseFaces( self, mesh ):
'''
Extract the faces from a blender mesh
'''
if len(mesh.uv_textures):
uv_tex = mesh.tessface_uv_textures.active
else:
uv_tex = None
is_flipped = self.bl_obj.scale[0]\
* self.bl_obj.scale[1]\
* self.bl_obj.scale[2] < 0
for face_idx in range(len(mesh.tessfaces)):
bl_face = mesh.tessfaces[face_idx]
if uv_tex:
uv_coords = uv_tex.data[face_idx].uv[:]
else:
uv_coords = None
surf = self.Surface(self.export_config, bl_face, self.ac_mats, mesh.show_double_sided, is_flipped, uv_coords)
self.surfaces.append(surf)
def _write( self, strm ):
if len(self.tex_name) > 0:
strm.write('texture "{0}"\n'.format(self.tex_name))
strm.write('texrep {0} {1}\n'.format(self.tex_rep[0], self.tex_rep[1]))
if len(self.vertices):
strm.write('numvert {0}\n'.format(len(self.vertices)))
for vert in self.vertices:
strm.write('{0:.7f} {1:.7f} {2:.7f}\n'.format( vert[0],
vert[1],
vert[2] ))
if len(self.surfaces):
strm.write('numsurf {0}\n'.format(len(self.surfaces)))
for surf in self.surfaces:
surf.write(strm)
# ------------------------------
class Surface:
def __init__( self,
export_config,
bl_face,
ac_mats,
is_two_sided,
is_flipped,
uv_coords ):
self.export_config = export_config
self.mat = 0 # material index for this surface
self.bl_face = bl_face
self.uv_coords = uv_coords
self.is_two_sided = is_two_sided
self.is_flipped = is_flipped
self.ac_surf_flags = self.SurfaceFlags(0, False, True)
self.parse_blender_face(bl_face, ac_mats)
def write(self, ac_file):
surf_flags = self.ac_surf_flags.getFlags()
ac_file.write('SURF {0:#X}\n'.format(surf_flags))
ac_file.write('mat {0}\n'.format(self.mat))
ac_file.write('refs {0}\n'.format(len(self.bl_face.vertices)))
r = range(len(self.bl_face.vertices))
if self.is_flipped:
r = reversed(r)
if self.uv_coords:
for n in r:
surf_ref = self.bl_face.vertices[n]
uv_ref = self.uv_coords[n]
ac_file.write('{0} {1:.6f} {2:.6f}\n'.format(surf_ref, uv_ref[0], uv_ref[1]))
else:
for n in r:
surf_ref = self.bl_face.vertices[n]
ac_file.write('{0} 0 0\n'.format(surf_ref))
def parse_blender_face(self, bl_face, ac_mats):
if bl_face.material_index in ac_mats:
self.mat = ac_mats[bl_face.material_index]
self.ac_surf_flags.smooth_shaded = bl_face.use_smooth
self.ac_surf_flags.twosided = self.is_two_sided
class SurfaceFlags:
def __init__( self,
surf_type,
is_smooth,
is_twosided ):
self.surf_type = surf_type
self.smooth_shaded = is_smooth
self.twosided = is_twosided
def getFlags(self):
n = self.surf_type & 0x0f
if self.smooth_shaded:
n = n | 0x10
if self.twosided:
n = n | 0x20
return n
# ------------------------------------------------------------------------------
class Group (Object):
'''
An object group
TODO maybe add an option to prevent exporting empty groups
'''
def __init__( self,
name,
bl_obj,
export_config,
local_transform = Matrix() ):
Object.__init__(self, name, 'group', bl_obj, export_config, local_transform)
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class Material:
'''
Container class that defines the material properties of the .ac MATERIAL
'''
def __init__( self,
name = 'DefaultWhite',
bl_mat = None,
export_config = None ):
self.name = name # string
self.rgb = [1.0, 1.0, 1.0] # [R,G,B]
self.amb = [0.2, 0.2, 0.2] # [R,G,B]
self.emis = [0.0, 0.0, 0.0] # [R,G,B]
self.spec = [0.5, 0.5, 0.5] # [R,G,B]
self.shi = 10 # integer
self.trans = 0 # float
if bl_mat:
self.name = bl_mat.name
self.rgb = bl_mat.diffuse_color
self.amb = [bl_mat.ambient, bl_mat.ambient, bl_mat.ambient]
if export_config.mircol_as_emis:
self.emis = bl_mat.mirror_color * bl_mat.emit
else:
self.emis = [bl_mat.emit, bl_mat.emit, bl_mat.emit]
self.spec = bl_mat.specular_intensity * bl_mat.specular_color
self.shi = bl_mat.specular_hardness
if bl_mat.use_transparency:
self.trans = 1.0 - bl_mat.alpha
else:
self.trans = 0.0
def write( self, strm ):
# MATERIAL %s rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f
strm.write('MATERIAL "{0}" rgb {1:.4f} {2:.4f} {3:.4f} amb {4:.4f} {5:.4f} {6:.4f} emis {7:.4f} {8:.4f} {9:.4f} spec {10:.4f} {11:.4f} {12:.4f} shi {13} trans {14:.4f}\n'.format(
self.name,
self.rgb[0], self.rgb[1], self.rgb[2],
self.amb[0], self.amb[1], self.amb[2],
self.emis[0], self.emis[1], self.emis[2],
self.spec[0], self.spec[1], self.spec[2],
self.shi,
self.trans,
))
def same_as( self, rhs ):
return self._feq(self.rgb[0], rhs.rgb[0]) and \
self._feq(self.rgb[1], rhs.rgb[1]) and \
self._feq(self.rgb[2], rhs.rgb[2]) and \
self._feq(self.amb[0], rhs.amb[0]) and \
self._feq(self.amb[1], rhs.amb[1]) and \
self._feq(self.amb[2], rhs.amb[2]) and \
self._feq(self.emis[0], rhs.emis[0]) and \
self._feq(self.emis[1], rhs.emis[1]) and \
self._feq(self.emis[2], rhs.emis[2]) and \
self._feq(self.spec[0], rhs.spec[0]) and \
self._feq(self.spec[1], rhs.spec[1]) and \
self._feq(self.spec[2], rhs.spec[2]) and \
self._feq(self.shi, rhs.shi) and \
self._feq(self.trans, rhs.trans)
def _feq(self, lhs, rhs):
return abs(rhs - lhs) < 0.0001

View file

@ -0,0 +1,271 @@
# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Most of this has been copied from the __init__.py file for the io_scene__xx
# folders of the standard 2.59 blender package and customised to
# act as a wrapper for the conventional AC3D importer/exporter
import time
import datetime
import bpy
import mathutils
from math import radians
from bpy.props import StringProperty, BoolProperty, FloatProperty, EnumProperty
from bpy_extras.io_utils import ImportHelper, ExportHelper, axis_conversion
bl_info = {
"name": "AC3D (.ac) format",
"description": "AC3D model exporter for blender.",
"author": "Chris Marr",
"version": (2,0),
"blender" : (2,6,0),
"api": 41098,
"location": "File > Import-Export",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Blender-AC3D",
"tracker_url": "http://projects.blender.org/tracker/index.php?func=detail&aid=29007&group_id=153&atid=468",
"category": "Import-Export"
}
# To support reload properly, try to access a package var, if it's there,
# reload everything
if "bpy" in locals():
import imp
if 'import_ac3d' in locals():
imp.reload(import_ac3d)
if 'export_ac3d' in locals():
imp.reload(export_ac3d)
def menu_func_import(self, context):
self.layout.operator(ImportAC3D.bl_idname, text='AC3D (.ac)')
def menu_func_export(self, context):
self.layout.operator(ExportAC3D.bl_idname, text='AC3D (.ac)')
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_import.append(menu_func_import)
bpy.types.INFO_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
class ImportAC3D(bpy.types.Operator, ImportHelper):
'''Import from AC3D file format (.ac)'''
bl_idname = 'import_scene.import_ac3d'
bl_label = 'Import AC3D'
bl_options = {'PRESET'}
filename_ext = '.ac'
filter_glob = StringProperty(default='*.ac', options={'HIDDEN'})
axis_forward = EnumProperty(
name="Forward",
items=(('X', "X Forward", ""),
('Y', "Y Forward", ""),
('Z', "Z Forward", ""),
('-X', "-X Forward", ""),
('-Y', "-Y Forward", ""),
('-Z', "-Z Forward", ""),
),
default='-Z',
)
axis_up = EnumProperty(
name="Up",
items=(('X', "X Up", ""),
('Y', "Y Up", ""),
('Z', "Z Up", ""),
('-X', "-X Up", ""),
('-Y', "-Y Up", ""),
('-Z', "-Z Up", ""),
),
default='Y',
)
use_transparency = BoolProperty(
name="Use Transparency",
description="Enable transparency for rendering if material alpha < 1.0",
default=True,
)
transparency_method = EnumProperty(
name="Transparency Method",
items=(('MASK', "Mask", ""),
('Z_TRANSPARENCY', "Z_Transp", ""),
('RAYTRACE', "RayTrace", ""),
),
default='Z_TRANSPARENCY',
)
use_auto_smooth = BoolProperty(
name="Auto Smooth",
description="Use object auto smooth if normal angles are beneath Crease angle",
default=True,
)
use_emis_as_mircol = BoolProperty(
name="Use Emis as Mirror colour",
description="Use Emission colour as Mirror colour",
default=True,
)
use_amb_as_mircol = BoolProperty(
name="Use Amb as Mirror colour",
description="Use Ambient colour as Mirror colour",
default=False,
)
display_transparency = BoolProperty(
name="Display Transparency",
description="Display transparency in main display",
default=True,
)
display_textured_solid = BoolProperty(
name="Display textured solid",
description="Show main window with textures applied (transparency works in only in normal direction)",
default=False,
)
def execute(self, context):
from . import import_ac3d
keywords = self.as_keywords(ignore=("axis_forward",
"axis_up",
"filter_glob",
))
global_matrix = axis_conversion(from_forward=self.axis_forward,
from_up=self.axis_up,
).to_4x4()
keywords["global_matrix"] = global_matrix
t = time.mktime(datetime.datetime.now().timetuple())
import_ac3d.ImportAC3D(self, context, **keywords)
t = time.mktime(datetime.datetime.now().timetuple()) - t
print('Finished importing in', t, 'seconds')
return {'FINISHED'}
class ExportAC3D(bpy.types.Operator, ExportHelper):
'''Export to AC3D file format (.ac)'''
bl_idname = 'export_scene.export_ac3d'
bl_label = 'Export AC3D'
bl_options = {'PRESET'}
filename_ext = '.ac'
filter_glob = StringProperty(
default='*.ac',
options={'HIDDEN'}
)
axis_forward = EnumProperty(
name="Forward",
items=(('X', "X Forward", ""),
('Y', "Y Forward", ""),
('Z', "Z Forward", ""),
('-X', "-X Forward", ""),
('-Y', "-Y Forward", ""),
('-Z', "-Z Forward", ""),
),
default='-Z',
)
axis_up = EnumProperty(
name="Up",
items=(('X', "X Up", ""),
('Y', "Y Up", ""),
('Z', "Z Up", ""),
('-X', "-X Up", ""),
('-Y', "-Y Up", ""),
('-Z', "-Z Up", ""),
),
default='Y',
)
use_render_layers = BoolProperty(
name="Only Render Layers",
description="Only export from selected render layers",
default=True,
)
use_selection = BoolProperty(
name="Selection Only",
description="Export selected objects only",
default=False,
)
skip_data = BoolProperty(
name="Skip Data",
description="don't export mesh names as data fields",
default=False,
)
global_coords = BoolProperty(
name="Global Co-ordinates",
description="Transform all vertices of all meshes to global coordinates",
default=False,
)
mircol_as_emis = BoolProperty(
name="Mirror col as Emis",
description="export mirror colour as emissive colour",
default=True,
)
mircol_as_amb = BoolProperty(
name="Mirror col as Amb",
description="export mirror colour as ambient colour",
default=False,
)
crease_angle = FloatProperty(
name="Default Crease Angle",
description="Default crease angle for exported .ac faces",
default=radians(179.0),
options={"ANIMATABLE"},
unit="ROTATION",
subtype="ANGLE",
)
# This behaviour from the original exporter - not applicable?
# no_split = BoolProperty(
# name="No Split",
# description="don't split meshes with multiple textures (or both textured and non-textured polygons)",
# default=True,
# )
def execute(self, context):
from . import export_ac3d
keywords = self.as_keywords(ignore=("axis_forward",
"axis_up",
"filter_glob",
"check_existing",
))
global_matrix = axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4()
keywords["global_matrix"] = global_matrix
t = time.mktime(datetime.datetime.now().timetuple())
export_ac3d.ExportAC3D(self, context, **keywords)
t = time.mktime(datetime.datetime.now().timetuple()) - t
print('Finished exporting in', t, 'seconds')
return {'FINISHED'}

View file

@ -0,0 +1,182 @@
# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
'''
This file is a complete reboot of the AC3D export script that is used to export .ac format file into blender.
Reference to the .ac format is found here:
http://www.inivis.com/ac3d/man/ac3dfileformat.html
Some noted points that are important for consideration:
- AC3D appears to use Left Handed axes, but with Y oriented "Up". Blender uses Right Handed axes, the export does provide a rotation matrix applied to the world object that corrects this, so "Up" in the blender becomes "Up" in the .AC file - it's configurable, so you can change how it rotates...
- AC3D supports only one texture per surface. This is a UV texture map, so only blenders texmap is exported
- Blender's Materials can have multiple textures per material - so a material + texure in AC3D requires a distinct and unique material in blender. The export uses a comparison of material properties to see if a material is the same as another one and then uses that material index for the .ac file.
TODO: Option to define "DefaultWhite" material
TODO: Optionally over-write existing textures
'''
from . import AC3D
import os, bpy
from math import radians
from mathutils import Matrix
def TRACE(message):
AC3D.TRACE(message)
class ExportConf:
def __init__(
self,
operator,
context,
filepath,
global_matrix,
use_selection,
use_render_layers,
skip_data,
global_coords,
mircol_as_emis,
mircol_as_amb,
crease_angle,
):
# Stuff that needs to be available to the working classes (ha!)
self.operator = operator
self.context = context
self.global_matrix = global_matrix
self.use_selection = use_selection
self.use_render_layers = use_render_layers
self.skip_data = skip_data
self.global_coords = global_coords
self.mircol_as_emis = mircol_as_emis
self.mircol_as_amb = mircol_as_amb
self.crease_angle = crease_angle
# used to determine relative file paths
self.exportdir = os.path.dirname(filepath)
self.ac_name = os.path.split(filepath)[1]
TRACE('Exporting to {0}'.format(self.ac_name))
class ExportAC3D:
def __init__(
self,
operator,
context,
filepath='',
global_matrix=None,
use_selection=False,
use_render_layers=True,
skip_data=False,
global_coords=False,
mircol_as_emis=True,
mircol_as_amb=False,
crease_angle=radians(179.0),
):
self.export_conf = ExportConf(
operator,
context,
filepath,
global_matrix,
use_selection,
use_render_layers,
skip_data,
global_coords,
mircol_as_emis,
mircol_as_amb,
crease_angle,
)
#TRACE("Global: {0}".format(global_matrix))
self.ac_mats = [AC3D.Material()]
self.ac_world = None
# Parsing the tree in a top down manner and check on the way down which
# objects are to be exported
self.world = AC3D.World('Blender_export__' + bpy.path.basename(filepath), self.export_conf)
self.parseLevel(self.world, [ob for ob in bpy.data.objects if ob.parent == None and not ob.library])
self.world.parse(self.ac_mats)
# dump the contents of the lists to file
ac_file = open(filepath, 'w')
ac_file.write('AC3Db\n')
for ac_mat in self.ac_mats:
ac_mat.write(ac_file)
#self.ac_world.write_ac_output(ac_file)
self.world.write(ac_file)
ac_file.close()
def parseLevel( self,
parent,
objects,
ignore_select = False,
local_transform = Matrix() ):
'''
Parse a level in the object hierarchy
'''
for ob in objects:
ac_ob = None
# Objects from libraries don't have the select flag set even if their
# proxy is selected. We therefore consider all objects from libraries as
# selected, as the only possibility to get them considered is if their
# proxy should be exported.
if (not self.export_conf.use_render_layers or ob.is_visible(self.export_conf.context.scene))\
and (not self.export_conf.use_selection or ob.select or ignore_select):
# We need to check for dupligroups first as every type of object can be
# converted to a dupligroup without removing the data from the old type.
if ob.dupli_type == 'GROUP':
ac_ob = AC3D.Group(ob.name, ob, self.export_conf, local_transform)
children = [child for child in ob.dupli_group.objects
if not child.parent
or not child.parent.name in ob.dupli_group.objects]
self.parseLevel(ac_ob, children, True, local_transform * ob.matrix_world)
elif ob.type in ['MESH', 'LATTICE', 'SURFACE', 'CURVE']:
ac_ob = AC3D.Poly(ob.name, ob, self.export_conf, local_transform)
elif ob.type == 'ARMATURE':
p = parent
for bone in ob.pose.bones:
for c in ob.children:
if c.parent_bone == bone.name:
ac_child = AC3D.Poly(c.name, c, self.export_conf, local_transform)
p.addChild(ac_child)
p = ac_child
if len(c.children):
self.parseLevel(p, c.children, ignore_select, local_transform)
continue
# elif ob.type == 'EMPTY':
# ac_ob = AC3D.Group(ob.name, ob, self.export_conf, local_transform)
else:
TRACE('Skipping object {0} (type={1})'.format(ob.name, ob.type))
if ac_ob:
parent.addChild(ac_ob)
next_parent = ac_ob
else:
# if link chain is broken (aka one element not exported) the object will
# be placed in global space (=world)
next_parent = self.world
if len(ob.children):
self.parseLevel(next_parent, ob.children, ignore_select, local_transform)

View file

@ -0,0 +1,693 @@
# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import os
import struct
import bpy
import csv
import mathutils
from mathutils import Vector, Euler
from math import radians
from bpy import *
from bpy_extras.image_utils import load_image
from bpy_extras.io_utils import unpack_list, unpack_face_list
'''
This file is a reboot of the AC3D import script that is used to import .ac format file into blender.
The original work carried out by Willian P Gerano was used to learn Python and Blender, but inherent issues and a desire to do better has prompted a complete re-write
Reference to the .ac format is found here:
http://www.inivis.com/ac3d/man/ac3dfileformat.html
Some noted points that are important for consideration:
- AC3D appears to use Left Handed axes, but with Y oriented "Up". Blender uses Right Handed axes, the import does provide a rotation matrix applied to the world object that corrects this, so "Up" in the AC file appears as "Up" in blender - it's configurable, so you can change how it rotates...
- AC3D supports only one texture per surface. This is a UV texture map, so it's imported as such in blender
- Blender's Materials can have multiple textures per material - so a material + texure in AC3D requires a distinct and unique material in blender (more of an issue for the export routine)
- AC3D supports individual face twosidedness, blender's twosidedness is per-object
TODO: Add setting so that .AC file is brought in relative to the 3D cursor
'''
DEBUG = True
def TRACE(message):
if DEBUG:
print(message)
class AcMat:
'''
Container class that defines the material properties of the .ac MATERIAL
'''
def __init__(self, name, rgb, amb, emis, spec, shi, trans, import_config):
if name == "":
name = "Default"
self.name = name # string
self.rgb = rgb # [R,G,B]
self.amb = amb # [R,G,B]
self.emis = emis # [R,G,B]
self.spec = spec # [R,G,B]
self.shi = shi # integer
self.trans = trans # float
self.bmat_keys = {} # dictionary list of blender materials
self.bmat_keys.setdefault(None)
self.bl_material = None # untextured material
self.import_config = import_config
def make_blender_mat(self, bl_mat):
bl_mat.diffuse_color = self.rgb
bl_mat.ambient = (self.amb[0] + self.amb[1] + self.amb[2]) / 3.0
bl_mat.emit = (self.emis[0] + self.emis[1] + self.emis[2]) / 3.0
bl_mat.specular_color = self.spec
bl_mat.specular_intensity = float(self.shi) / 100
bl_mat.alpha = 1.0 - self.trans
if bl_mat.alpha < 1.0:
bl_mat.use_transparency = self.import_config.use_transparency
bl_mat.transparency_method = self.import_config.transparency_method
return bl_mat
'''
looks for a matching blender material (optionally with a texture), adds it if it doesn't exist
'''
def get_blender_material(self, tex_name=''):
bl_mat = None
tex_slot = None
if tex_name == '':
bl_mat = self.bl_material
if bl_mat == None:
bl_mat = bpy.data.materials.new(self.name)
bl_mat = self.make_blender_mat(bl_mat)
self.bl_material = bl_mat
else:
if tex_name in self.bmat_keys:
bl_mat = self.bmat_keys[tex_name]
else:
bl_mat = bpy.data.materials.new(self.name)
bl_mat = self.make_blender_mat(bl_mat)
bl_mat.use_face_texture = True
bl_mat.use_face_texture_alpha = True
tex_slot = bl_mat.texture_slots.add()
tex_slot.texture = self.get_blender_texture(tex_name)
tex_slot.texture_coords = 'UV'
tex_slot.alpha_factor = 1.0
tex_slot.use_map_alpha = True
tex_slot.use = True
tex_slot.uv_layer = tex_name
self.bmat_keys[tex_name] = bl_mat
return bl_mat
'''
looks for the image in blender, adds it if it doesn't exist, returns the image to the callee
'''
def get_blender_image(self, tex_name):
bl_image = None
if tex_name in bpy.data.images:
bl_image = bpy.data.images[tex_name]
else:
found = False
base_name = bpy.path.basename(tex_name)
for path in [ tex_name,
os.path.join(self.import_config.importdir, tex_name),
os.path.join(self.import_config.importdir, base_name) ]:
if os.path.exists(path):
found = True
try:
bl_image = bpy.data.images.load(path)
except:
TRACE("Failed to load texture: {0}".format(tex_name))
if not found:
TRACE("Failed to locate texture: {0}".format(tex_name))
return bl_image
'''
looks for the blender texture, adds it if it doesn't exist
'''
def get_blender_texture(self, tex_name):
bl_tex = None
if tex_name in bpy.data.textures:
bl_tex = bpy.data.textures[tex_name]
else:
bl_tex = bpy.data.textures.new(tex_name, 'IMAGE')
bl_tex.image = self.get_blender_image(tex_name)
bl_tex.use_preview_alpha = True
return bl_tex
class AcObj:
'''
Container class for a .ac OBJECT
'''
def __init__(self, ob_type, ac_file, import_config, parent = None):
self.type = ob_type # Type of object
self.ac_parent = parent # reference to the parent object (if the object is World, then this should be None)
self.name = '' # name of the object
self.data = '' # custom data
self.tex_name = '' # texture name (filename of texture)
self.texrep = [1,1] # texture repeat
self.texoff = [0,0] # texture offset
self.location = [0,0,0] # translation location of the center relative to the parent object
self.rotation = mathutils.Matrix(([1,0,0],[0,1,0],[0,0,1])) # 3x3 rotational matrix for vertices
self.url = '' # url of the object (??!)
self.crease = 30 # crease angle for smoothing
self.vert_list = [] # list of Vector(X,Y,Z) objects
self.surf_list = [] # list of attached surfaces
self.face_list = [] # flattened surface list
self.edge_list = [] # spare edge list (handles poly lines etc)
self.face_mat_list = [] # flattened surface material index list
self.children = []
self.bl_mat_dict = {} # Dictionary of ac_material index/texture pair to blender mesh material index
self.bl_obj = None # Blender object
self.import_config = import_config
self.tokens = {
'numvert': self.read_vertices,
'numsurf': self.read_surfaces,
'name': self.read_name,
'data': self.read_data,
'kids': self.read_children,
'loc': self.read_location,
'rot': self.read_rotation,
'texture': self.read_texture,
'texrep': self.read_texrep,
'texoff': self.read_texoff,
'subdiv': self.read_subdiv,
'crease': self.read_crease
}
self.read_ac_object(ac_file)
'''
Read the object lines and dump them into this object, making hierarchial attachments to parents
'''
def read_ac_object(self, ac_file):
bDone = False
while not bDone:
line = ac_file.readline()
if line == '':
break
toks = line.strip().split()
if len(toks)>0:
if toks[0] in self.tokens.keys():
bDone = self.tokens[toks[0]](ac_file,toks)
else:
bDone = True
def read_vertices(self, ac_file, toks):
vertex_count = int(toks[1])
for n in range(vertex_count):
line = ac_file.readline()
line = line.strip().split()
self.vert_list.append(self.import_config.global_matrix * Vector([float(x) for x in line]))
def read_surfaces(self, ac_file, toks):
surf_count = int(toks[1])
for n in range(surf_count):
line = ac_file.readline()
if line=='':
break
line = line.strip().split()
if line[0] == 'SURF':
surf = AcSurf(line[1], ac_file, self.import_config)
# TODO check this fix which just ignores everything but quads and triangles
if( len(surf.refs) in [3,4] ):
self.surf_list.append(surf)
else:
TRACE("Ignoring surface (vertex-count: {0})".format(len(surf.refs)))
def read_name(self, ac_file, toks):
self.name=toks[1].strip('"')
return False
def read_data(self, ac_file, toks):
line = ac_file.readline()
self.data=line[:int(toks[1])]
return False
def read_location(self, ac_file, toks):
self.location=(self.import_config.global_matrix * Vector([float(x) for x in toks[1:4]]))
return False
def read_rotation(self, ac_file, toks):
self.rotation = mathutils.Matrix(([float(x) for x in toks[1:4]], [float(x) for x in toks[4:7]], [float(x) for x in toks[7:10]]))
# TODO check
# rotation = mathutils.Matrix(( [float(x) for x in toks[1:4]],
# [float(x) for x in toks[4:7]],
# [float(x) for x in toks[7:10]] )).to_quaternion()
# rotation.axis = self.import_config.global_matrix * rotation.axis
# self.rotation = rotation.to_matrix()
return False
def read_texture(self, ac_file, toks):
self.tex_name=toks[1].strip('"')
return False
def read_texrep(self, ac_file, toks):
self.texrep=toks[1:2]
return False
def read_texoff(self, ac_file, toks):
self.texoff=toks[1:2]
return False
def read_subdiv(self, ac_file, toks):
self.subdiv=int(toks[1])
return False
def read_crease(self, ac_file, toks):
self.crease=float(toks[1])
return False
def read_children(self, ac_file, toks):
num_kids = int(toks[1])
for n in range(num_kids):
line = ac_file.readline()
if line == '':
break
line = line.strip().split()
self.children.append(AcObj(line[1].strip('"'), ac_file, self.import_config, self))
# This is assumed to be the last thing in the list of things to read
# returning True indicates to cease parsing this object
return True
'''
This function does the work of creating an object in blender and configuring it correctly
'''
def create_blender_object(self, ac_matlist, str_pre, bLevelLinked):
if self.type == 'world':
self.name = self.import_config.ac_name
self.rotation = self.import_config.global_matrix
me = None
if self.type == 'group':
# Create an empty object
self.bl_obj = bpy.data.objects.new(self.name, None)
if self.type == 'poly':
meshname = self.name
if len(self.data)>0:
meshname = self.data
me = bpy.data.meshes.new(meshname)
self.bl_obj = bpy.data.objects.new(self.name, me)
# setup parent object
if self.ac_parent:
self.bl_obj.parent = self.ac_parent.bl_obj
# make sure we have something to work with
if self.vert_list and me:
me.use_auto_smooth = self.import_config.use_auto_smooth
me.auto_smooth_angle = radians(self.crease)
for surf in self.surf_list:
surf_edges = surf.get_edges()
surf_face = surf.get_faces()
for edge in surf_edges:
self.edge_list.append(edge)
if surf.flags.type == 0:
# test for invalid face (ie, >4 vertices)
if len(surf.refs) > 4 or len(surf.refs) < 3:
# not bringing in faces (assumed that there are none in a poly-line)
TRACE("Ignoring surface (vertex-count: {0})".format(len(surf.refs)))
else:
self.face_list.append(surf_face)
# Material index is 1 based, the list we built is 0 based
ac_material = ac_matlist[surf.mat_index]
bl_material = ac_material.get_blender_material(self.tex_name)
if bl_material == None:
TRACE("Error getting material {0} '{1}'".format(surf.mat_index, self.tex_name))
fm_index = 0
if not bl_material.name in me.materials:
me.materials.append(bl_material)
fm_index = len(me.materials)-1
else:
for mat in me.materials:
if mat == bl_material:
continue
fm_index += 1
if fm_index > len(me.materials):
TRACE("Failed to find material index")
fm_index = 0
self.face_mat_list.append(fm_index)
else:
# treating as a polyline (nothing more to do)
pass
me.vertices.add(len(self.vert_list))
me.tessfaces.add(len(self.face_list))
# verts_loc is a list of (x, y, z) tuples
me.vertices.foreach_set("co", unpack_list(self.vert_list))
# faces is a list of (vert_indices, texco_indices, ...) tuples
me.tessfaces.foreach_set("vertices_raw", unpack_face_list(self.face_list))
# face_mat = [m for m in self.face_mat_list]
# me.tessfaces.foreach_set("material_index", face_mat)
# del face_mat
if len(self.tex_name):
me.tessface_uv_textures.new()
# uv_tex.active = True
# uv_tex.active_render = True
else:
uv_tex = None
two_sided_lighting = False
for i, face in enumerate(self.face_list):
blender_face = me.tessfaces[i]
surf = self.surf_list[i]
blender_face.use_smooth = surf.flags.shaded
# If one surface is twosided, they all will be...
two_sided_lighting |= surf.flags.two_sided
if len(self.tex_name) and len(surf.uv_refs) >= 3:
blender_tface = me.tessface_uv_textures[0].data[i]
blender_tface.uv1 = surf.uv_refs[0]
blender_tface.uv2 = surf.uv_refs[1]
blender_tface.uv3 = surf.uv_refs[2]
if len(surf.uv_refs) > 3:
blender_tface.uv4 = surf.uv_refs[3]
surf_material = me.materials[self.face_mat_list[i]]
blender_tface.image = surf_material.texture_slots[0].texture.image
# uv_tex.data[f_index].use_image = True
me.show_double_sided = two_sided_lighting
self.bl_obj.show_transparent = self.import_config.display_transparency
if self.bl_obj:
self.bl_obj.rotation_euler = self.rotation.to_euler()
self.bl_obj.location = self.location
self.import_config.context.scene.objects.link(self.bl_obj)
# There's a bug somewhere - this ought to work....
self.import_config.context.scene.objects.active = self.bl_obj
# bpy.ops.object.origin_set('ORIGIN_GEOMETRY', 'MEDIAN')
TRACE("{0}+-{1} ({2})".format(str_pre, self.name, self.data))
# Add any children
str_pre_new = ""
bUseLink = True
for obj in self.children:
if bLevelLinked:
str_pre_new = str_pre + "| "
else:
str_pre_new = str_pre + " "
if self.children.index(obj) == len(self.children)-1:
bUseLink = False
obj.create_blender_object(ac_matlist, str_pre_new, bUseLink)
if me:
# me.calc_normals()
me.validate()
me.update(calc_edges=True)
class AcSurf:
class AcSurfFlags:
def __init__(self, flags):
self.type = 0 # Surface Type: 0=Polygon, 1=closedLine, 2=Line
self.shaded = False
self.two_sided = False
i = int(flags,16)
self.type = i & 0xF
i = i >> 4
if i&1:
self.shaded = True
if i&2:
self.two_sided = True
'''
Container class for surface definition within a parent object
'''
def __init__(self, flags, ac_file, import_config):
self.flags = self.AcSurfFlags(flags) # surface flags
self.mat_index = 0 # default material
self.refs = [] # list of indexes into the parent objects defined vertexes with defined UV coordinates
self.uv_refs = []
self.tokens = {
'mat': self.read_surf_material,
'refs': self.read_surf_refs,
}
self.import_config = import_config
self.read_ac_surfaces(ac_file)
def read_ac_surfaces(self, ac_file):
surf_done=False
while not surf_done:
line = ac_file.readline()
if line=='':
break
toks = line.split()
if len(toks)>0:
if toks[0] in self.tokens.keys():
surf_done = self.tokens[toks[0]](ac_file,toks)
else:
surf_done = True
def read_surf_material(self, ac_file, tokens):
self.mat_index = int(tokens[1])
return False
def read_surf_refs(self, ac_file, tokens):
num_refs = int(tokens[1])
for n in range(num_refs):
line = ac_file.readline()
line = line.strip().split()
self.refs.append(int(line[0]))
self.uv_refs.append([float(x) for x in line[1:3]])
return True
def get_faces(self):
# convert refs and surface type to faces
surf_faces = []
# make sure it's a face type polygon and that there's the right number of vertices
if self.flags.type == 0 and len(self.refs) in [3,4]:
surf_faces = self.refs
return surf_faces
def get_edges(self):
# convert refs and surface type to edges
surf_edges = []
if self.flags.type != 0:
# poly-line
for x in range(len(self.refs)-1):
surf_edges.append([self.refs[x],self.refs[x+1]])
if self.flags.type == 1:
# closed poly-line
surf_edges.append([self.refs[len(self.refs)-1],self.refs[0]])
return surf_edges
class ImportConf:
def __init__(
self,
operator,
context,
filepath,
global_matrix,
use_transparency,
transparency_method,
use_auto_smooth,
use_emis_as_mircol,
use_amb_as_mircol,
display_transparency,
display_textured_solid,
):
# Stuff that needs to be available to the working classes (ha!)
self.operator = operator
self.context = context
self.global_matrix = global_matrix
self.use_transparency = use_transparency
self.transparency_method = transparency_method
self.use_auto_smooth = use_auto_smooth
self.use_emis_as_mircol = use_emis_as_mircol
self.use_amb_as_mircol = use_amb_as_mircol
self.display_transparency = display_transparency
self.display_textured_solid = display_textured_solid
# used to determine relative file paths
self.importdir = os.path.dirname(filepath)
self.ac_name = os.path.split(filepath)[1]
TRACE("Importing {0}".format(self.ac_name))
class ImportAC3D:
def __init__(
self,
operator,
context,
filepath="",
use_image_search=False,
global_matrix=None,
use_transparency=True,
transparency_method='Z_TRANSPARENCY',
use_auto_smooth=True,
use_emis_as_mircol=True,
use_amb_as_mircol=False,
display_transparency=True,
display_textured_solid=False,
):
self.import_config = ImportConf(
operator,
context,
filepath,
global_matrix,
use_transparency,
transparency_method,
use_auto_smooth,
use_emis_as_mircol,
use_amb_as_mircol,
display_transparency,
display_textured_solid,
)
self.tokens = {
'MATERIAL': self.read_material,
'OBJECT': self.read_object,
}
self.oblist = []
self.matlist = []
operator.report({'INFO'}, "Attempting import: {file}".format(file=filepath))
# Check to make sure we're working with a valid AC3D file
ac_file = open(filepath, 'r')
self.header = ac_file.readline().strip()
if len(self.header) != 5:
operator.report({'ERROR'},"Invalid file header length: {0}".format(self.header))
ac_file.close()
return None
#pull out the AC3D file header
AC3D_header = self.header[:4]
AC3D_ver = self.header[4:5]
if AC3D_header != 'AC3D':
operator.report({'ERROR'},"Invalid file header: {0}".format(self.header))
ac_file.close()
return None
self.read_ac_file(ac_file)
ac_file.close()
self.create_blender_data()
# Display as either textured solid (transparency only works in one direction) or as textureless solids (transparency works)
for bl_screen in bpy.data.screens:
for bl_area in bl_screen.areas:
for bl_space in bl_area.spaces:
if bl_space.type == 'VIEW_3D':
bl_space.show_textured_solid = self.import_config.display_textured_solid
return None
'''
Simplifies the reporting of errors to the user
'''
def report_error(self, message):
TRACE(message)
self.import_config.operator.report({'ERROR'},message)
'''
read our validated .ac file
'''
def read_ac_file(self, ac_file):
reader = csv.reader(ac_file, delimiter=' ', skipinitialspace=True)
try:
for row in reader:
# See if this is a valid token and pass the file handle and the current line to our function
if row[0] in self.tokens.keys():
self.tokens[row[0]](ac_file,row)
else:
self.report_error("invalid token: {tok} ({ln})".format(tok=row[0], ln=row))
except csv.Error(e):
self.report_error('AC3D import error, line %d: %s' % (reader.line_num, e))
'''
Take the passed in line and interpret as a .ac material
'''
def read_material(self, ac_file, line):
# MATERIAL %s rgb %f %f %f amb %f %f %f emis %f %f %f spec %f %f %f shi %d trans %f
self.matlist.append(AcMat(line[1],
[float(x) for x in line[3:6]],
[float(x) for x in line[7:10]],
[float(x) for x in line[11:14]],
[float(x) for x in line[15:18]],
float(line[19]), # it should be int but float seems to be used sometimes
float(line[21]),
self.import_config,
))
'''
Read the Object definition (including child objects)
'''
def read_object(self, ac_file, line):
# OBJECT %s
self.oblist.append(AcObj(line[1], ac_file, self.import_config))
'''
Reads the data imported from the file and creates blender data
'''
def create_blender_data(self):
# go through the list of objects
bUseLink = True
for obj in self.oblist:
if self.oblist.index(obj) == len(self.oblist)-1:
bUseLink = False
obj.create_blender_object(self.matlist, "", bUseLink)