3
""" Registration info for Blender menus:
7
Tip: 'Import an AC3D (.ac) file.'
10
__author__ = "Willian P. Germano"
11
__url__ = ("blender", "blenderartists.org", "AC3D's homepage, http://www.ac3d.org",
12
"PLib 3d gaming lib, http://plib.sf.net")
13
__version__ = "2.43.1 2007-02-21"
16
This script imports AC3D models into Blender.
18
AC3D is a simple and affordable commercial 3d modeller also built with OpenGL.
19
The .ac file format is an easy to parse text format well supported,
20
for example, by the PLib 3d gaming library.
23
UV-textured meshes with hierarchy (grouping) information.
26
The url tag is irrelevant for Blender.
29
- Some objects may be imported with wrong normals due to wrong information in the model itself. This can be noticed by strange shading, like darker than expected parts in the model. To fix this, select the mesh with wrong normals, enter edit mode and tell Blender to recalculate the normals, either to make them point outside (the usual case) or inside.<br>
32
- display transp (toggle): if "on", objects that have materials with alpha < 1.0 are shown with translucency (transparency) in the 3D View.<br>
33
- subdiv (toggle): if "on", ac3d objects meant to be subdivided receive a SUBSURF modifier in Blender.<br>
34
- textures dir (string): if non blank, when imported texture paths are
35
wrong in the .ac file, Blender will also look for them at this dir.
38
- When looking for assigned textures, Blender tries in order: the actual
39
paths from the .ac file, the .ac file's dir and the default textures dir path
40
users can configure (see config options above).
43
# $Id: ac3d_import.py 14530 2008-04-23 14:04:05Z campbellbarton $
45
# --------------------------------------------------------------------------
46
# AC3DImport version 2.43.1 Feb 21, 2007
47
# Program versions: Blender 2.43 and AC3Db files (means version 0xb)
48
# changed: better triangulation of ngons, more fixes to support bad .ac files,
49
# option to display transp mats in 3d view, support "subdiv" tag (via SUBSURF modifier)
50
# --------------------------------------------------------------------------
51
# Thanks: Melchior Franz for extensive bug testing and reporting, making this
52
# version cope much better with old or bad .ac files, among other improvements;
53
# Stewart Andreason for reporting a serious crash.
54
# --------------------------------------------------------------------------
55
# ***** BEGIN GPL LICENSE BLOCK *****
57
# Copyright (C) 2004-2007: Willian P. Germano, wgermano _at_ ig.com.br
59
# This program is free software; you can redistribute it and/or
60
# modify it under the terms of the GNU General Public License
61
# as published by the Free Software Foundation; either version 2
62
# of the License, or (at your option) any later version.
64
# This program is distributed in the hope that it will be useful,
65
# but WITHOUT ANY WARRANTY; without even the implied warranty of
66
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67
# GNU General Public License for more details.
69
# You should have received a copy of the GNU General Public License
70
# along with this program; if not, write to the Free Software Foundation,
71
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
73
# ***** END GPL LICENCE BLOCK *****
74
# --------------------------------------------------------------------------
76
from math import radians
79
from Blender import Scene, Object, Mesh, Lamp, Registry, sys as bsys, Window, Image, Material, Modifier
80
from Blender.sys import dirsep
81
from Blender.Mathutils import Vector, Matrix, Euler
82
from Blender.Geometry import PolyFill
84
# Default folder for AC3D textures, to override wrong paths, change to your
85
# liking or leave as "":
93
'DISPLAY_TRANSP': 'Turn transparency on in the 3d View for objects using materials with alpha < 1.0.',
94
'SUBDIV': 'Apply a SUBSURF modifier to objects meant to appear subdivided.',
95
'TEXTURES_DIR': 'Additional folder to look for missing textures.'
98
def update_registry():
99
global TEXTURES_DIR, DISPLAY_TRANSP
100
rd = dict([('tooltips', tooltips), ('TEXTURES_DIR', TEXTURES_DIR), ('DISPLAY_TRANSP', DISPLAY_TRANSP), ('SUBDIV', SUBDIV)])
101
Registry.SetKey('ac3d_import', rd, True)
103
rd = Registry.GetKey('ac3d_import', True)
109
TEXTURES_DIR = rd['TEXTURES_DIR']
110
DISPLAY_TRANSP = rd['DISPLAY_TRANSP']
111
SUBDIV = rd['SUBDIV']
114
else: update_registry()
117
oldtexdir = TEXTURES_DIR
118
if dirsep == '/': TEXTURES_DIR = TEXTURES_DIR.replace('\\', '/')
119
if TEXTURES_DIR[-1] != dirsep: TEXTURES_DIR = "%s%s" % (TEXTURES_DIR, dirsep)
120
if oldtexdir != TEXTURES_DIR: update_registry()
124
rd = Registry.GetKey('General', True)
126
if rd.has_key('verbose'):
127
VERBOSE = rd['verbose']
132
# Matrix to align ac3d's coordinate system with Blender's one,
133
# it's a -90 degrees rotation around the x axis:
134
AC_TO_BLEND_MATRIX = Matrix([1, 0, 0], [0, 0, 1], [0, -1, 0])
147
AC_OB_BAD_TYPES_LIST = [] # to hold references to unknown (wrong) ob types
151
if VERBOSE: print msg
153
def euler_in_radians(eul):
154
"Used while there's a bug in the BPY API"
155
eul.x = radians(eul.x)
156
eul.y = radians(eul.y)
157
eul.z = radians(eul.z)
162
def __init__(self, type):
183
self.bl_obj = None # the actual Blender object created from this data
187
def __init__(self, filename):
191
self.scene = Scene.GetCurrent()
195
self.importdir = bsys.dirname(filename)
197
file = open(filename, 'r')
198
except IOError, (errno, strerror):
199
errmsg = "IOError #%s: %s" % (errno, strerror)
200
Blender.Draw.PupMenu('ERROR: %s' % errmsg)
203
header = file.read(5)
204
header, version = header[:4], header[-1]
207
errmsg = 'AC3D header not found (invalid file)'
208
Blender.Draw.PupMenu('ERROR: %s' % errmsg)
212
inform('AC3D file version 0x%s.' % version)
213
inform('This importer is for version 0xb, so it may fail.')
215
self.token = {'OBJECT': self.parse_obj,
216
'numvert': self.parse_vert,
217
'numsurf': self.parse_surf,
218
'name': self.parse_name,
219
'data': self.parse_data,
220
'kids': self.parse_kids,
221
'loc': self.parse_loc,
222
'rot': self.parse_rot,
223
'MATERIAL': self.parse_mat,
224
'texture': self.parse_tex,
225
'texrep': self.parse_texrep,
226
'texoff': self.parse_texoff,
227
'subdiv': self.parse_subdiv,
228
'crease': self.parse_crease}
232
self.kidsnumlist = []
235
self.lines = file.readlines()
236
self.lines.append('')
240
self.testAC3DImport()
242
def parse_obj(self, value):
243
kidsnumlist = self.kidsnumlist
245
while not kidsnumlist[-1]:
248
self.dad = self.dad.dad
250
inform('Ignoring unexpected data at end of file.')
251
return -1 # bad file with more objects than reported
253
if value in AC_OB_TYPES:
254
new = Obj(AC_OB_TYPES[value])
256
if value not in AC_OB_BAD_TYPES_LIST:
257
AC_OB_BAD_TYPES_LIST.append(value)
258
inform('Unexpected object type keyword: "%s". Assuming it is of type: "poly".' % value)
259
new = Obj(AC_OB_TYPES['poly'])
262
self.objlist.append(new)
264
def parse_kids(self, value):
267
self.kidsnumlist.append(kids)
268
self.dad = self.objlist[-1]
269
self.objlist[-1].kids = kids
271
def parse_name(self, value):
272
name = value.split('"')[1]
273
self.objlist[-1].name = name
275
def parse_data(self, value):
276
data = self.lines[self.i].strip()
277
self.objlist[-1].data = data
279
def parse_tex(self, value):
280
line = self.lines[self.i - 1] # parse again to properly get paths with spaces
281
texture = line.split('"')[1]
282
self.objlist[-1].tex = texture
284
def parse_texrep(self, trash):
285
trep = self.lines[self.i - 1]
287
trep = [float(trep[1]), float(trep[2])]
288
self.objlist[-1].texrep = trep
289
self.objlist[-1].texoff = [0, 0]
291
def parse_texoff(self, trash):
292
toff = self.lines[self.i - 1]
294
toff = [float(toff[1]), float(toff[2])]
295
self.objlist[-1].texoff = toff
297
def parse_mat(self, value):
300
line = lines[i].split()
302
mat_col = mat_amb = mat_emit = mat_spec_col = [0,0,0]
306
while line[0] == 'MATERIAL':
307
mat_name = line[1].split('"')[1]
308
mat_col = map(float,[line[3],line[4],line[5]])
309
v = map(float,[line[7],line[8],line[9]])
310
mat_amb = (v[0]+v[1]+v[2]) / 3.0
311
v = map(float,[line[11],line[12],line[13]])
312
mat_emit = (v[0]+v[1]+v[2]) / 3.0
313
mat_spec_col = map(float,[line[15],line[16],line[17]])
314
mat_spec = float(line[19]) / 64.0
315
mat_alpha = float(line[-1])
316
mat_alpha = 1 - mat_alpha
317
self.mlist.append([mat_name, mat_col, mat_amb, mat_emit, mat_spec_col, mat_spec, mat_alpha])
319
line = lines[i].split()
323
def parse_rot(self, trash):
325
ob = self.objlist[-1]
326
rot = self.lines[i].split(' ', 1)[1]
327
rot = map(float, rot.split())
328
matrix = Matrix(rot[:3], rot[3:6], rot[6:])
330
size = matrix.scalePart() # vector
333
def parse_loc(self, trash):
335
loc = self.lines[i].split(' ', 1)[1]
336
loc = map(float, loc.split())
337
self.objlist[-1].loc = Vector(loc)
339
def parse_crease(self, value):
340
# AC3D: range is [0.0, 180.0]; Blender: [1, 80]
342
self.objlist[-1].crease = int(value)
344
def parse_subdiv(self, value):
345
self.objlist[-1].subdiv = int(value)
347
def parse_vert(self, value):
350
obj = self.objlist[-1]
355
line = lines[i].split()
356
line = map(float, line)
361
if vlist: # prepend a vertex at 1st position to deal with vindex 0 issues
362
vlist.insert(0, line)
366
def parse_surf(self, value):
371
obj = self.objlist[-1]
373
matlist = obj.matlist
377
badface_notpoly = badface_multirefs = 0
380
flags = lines[i].split()[1][2:]
382
flaghigh = int(flags[0])
383
flaglow = int(flags[1])
386
flaglow = int(flags[0])
388
is_smooth = flaghigh & 1
389
twoside = flaghigh & 2
390
nextline = lines[i+1].split()
391
if nextline[0] != 'mat': # the "mat" line may be missing (found in one buggy .ac file)
393
if not matid in matlist: matlist.append(matid)
396
matid = int(nextline[1])
397
if not matid in matlist: matlist.append(matid)
398
nextline = lines[i+2].split()
400
refs = int(nextline[1])
409
line = lines[i].split()
410
v = int(line[0]) + 1 # + 1 to avoid vindex == 0
411
uv = [float(line[1]), float(line[2])]
413
fuv.append(Vector(uv))
417
if flaglow: # it's a line or closed line, not a polygon
418
while len(face) >= 2:
423
if flaglow == 1 and edges: # closed line
424
face = [edges[-1][-1], edges[0][0]]
429
# check for bad face, that references same vertex more than once
432
# less than 3 vertices, not a face
434
elif sum(map(face.count, face)) != lenface:
435
# multiple references to the same vertex
436
badface_multirefs += 1
437
else: # ok, seems fine
438
if len(face) > 4: # ngon, triangulate it
441
polyline.append(Vector(vlist[vi]))
442
tris = PolyFill([polyline])
444
tri = [face[t[0]], face[t[1]], face[t[2]]]
445
triuvs = [fuv[t[0]], fuv[t[1]], fuv[t[2]]]
452
obj.flist_cfg.extend([[matid, is_smooth, twoside]] * len(faces))
453
obj.flist_v.extend(faces)
454
obj.flist_uv.extend(fuvs)
455
obj.elist.extend(edges) # loose edges
459
if badface_notpoly or badface_multirefs:
460
inform('Object "%s" - ignoring bad faces:' % obj.name)
462
inform('\t%d face(s) with less than 3 vertices.' % badface_notpoly)
463
if badface_multirefs:
464
inform('\t%d face(s) with multiple references to a same vertex.' % badface_multirefs)
468
def parse_file(self):
471
line = lines[i].split()
475
for k in self.token.keys():
482
result = self.token[kw](line[1])
484
break # bad .ac file, stop parsing
486
line = lines[i].split()
488
# for each group of meshes we try to find one that can be used as
489
# parent of the group in Blender.
490
# If not found, we can use an Empty as parent.
491
def found_parent(self, groupname, olist):
492
l = [o for o in olist if o.type == AC_POLY \
493
and not o.kids and not o.rot and not o.loc]
496
if o.name == groupname:
501
def build_hierarchy(self):
502
blmatrix = AC_TO_BLEND_MATRIX
504
olist = self.objlist[1:]
514
children = newlist[-kids:]
515
newlist = newlist[:-kids]
516
if o.type == AC_GROUP:
517
parent = self.found_parent(o.name, children)
519
children.remove(parent)
520
o.bl_obj = parent.bl_obj
521
else: # not found, use an empty
522
empty = scene.objects.new('Empty', o.name)
525
bl_children = [c.bl_obj for c in children if c.bl_obj != None]
527
o.bl_obj.makeParent(bl_children, 0, 1)
528
for child in children:
530
if not blob: continue
532
eul = euler_in_radians(child.rot.toEuler())
535
blob.size = child.size
537
child.loc = Vector(0.0, 0.0, 0.0)
538
blob.setLocation(child.loc)
542
for o in newlist: # newlist now only has objs w/o parents
547
o.bl_obj.size = o.size
549
blob.setEuler([1.5707963267948966, 0, 0])
551
matrix = o.rot * blmatrix
552
eul = euler_in_radians(matrix.toEuler())
557
o.loc = Vector(0.0, 0.0, 0.0)
558
blob.setLocation(o.loc) # forces DAG update, so we do it even for 0, 0, 0
560
# XXX important: until we fix the BPy API so it doesn't increase user count
561
# when wrapping a Blender object, this piece of code is needed for proper
562
# object (+ obdata) deletion in Blender:
563
for o in self.objlist:
567
def testAC3DImport(self):
569
FACE_TWOSIDE = Mesh.FaceModes['TWOSIDE']
570
FACE_TEX = Mesh.FaceModes['TEX']
571
MESH_AUTOSMOOTH = Mesh.Modes['AUTOSMOOTH']
573
MAT_MODE_ZTRANSP = Material.Modes['ZTRANSP']
574
MAT_MODE_TRANSPSHADOW = Material.Modes['TRANSPSHADOW']
578
bl_images = {} # loaded texture images
579
missing_textures = [] # textures we couldn't find
581
objlist = self.objlist[1:] # skip 'world'
584
has_transp_mats = False
585
for mat in self.mlist:
587
m = Material.New(name)
588
m.rgbCol = (mat[1][0], mat[1][1], mat[1][2])
591
m.specCol = (mat[4][0], mat[4][1], mat[4][2])
595
m.mode |= MAT_MODE_ZTRANSP
596
has_transp_mats = True
601
mat.mode |= MAT_MODE_TRANSPSHADOW
603
obj_idx = 0 # index of current obj in loop
605
if obj.type == AC_GROUP:
607
elif obj.type == AC_LIGHT:
608
light = Lamp.New('Lamp')
609
object = scene.objects.new(light, obj.name)
613
light.name = obj.data
618
# old .ac files used empty meshes as groups, convert to a real ac group
619
if not obj.vlist and obj.kids:
624
object = scene.objects.new(mesh, obj.name)
627
if obj.data: mesh.name = obj.data
628
mesh.degr = obj.crease # will auto clamp to [1, 80]
630
if not obj.vlist: # no vertices? nothing more to do
633
mesh.verts.extend(obj.vlist)
637
if bmat.index(mat) in obj.matlist:
638
objmat_indices.append(bmat.index(mat))
639
mesh.materials += [mat]
640
if DISPLAY_TRANSP and mat.alpha < 1.0:
647
mesh.faces.extend(obj.flist_v)
649
facesnum = len(mesh.faces)
651
if facesnum == 0: # shouldn't happen, of course
656
# checking if the .ac file had duplicate faces (Blender ignores them)
657
if facesnum != len(obj.flist_v):
658
# it has, ugh. Let's clean the uv list:
659
lenfl = len(obj.flist_v)
661
uvlist = obj.flist_uv
662
cfglist = obj.flist_cfg
666
while fi > 0: # remove data related to duplicates
668
if flist[fi] in flist[:fi]:
674
if obj.tex in bl_images.keys():
675
img = bl_images[obj.tex]
676
elif obj.tex not in missing_textures:
679
baseimgname = bsys.basename(objtex)
680
if bsys.exists(objtex) == 1:
682
elif bsys.exists(bsys.join(self.importdir, objtex)):
683
texfname = bsys.join(self.importdir, objtex)
685
if baseimgname.find('\\') > 0:
686
baseimgname = bsys.basename(objtex.replace('\\','/'))
687
objtex = bsys.join(self.importdir, baseimgname)
688
if bsys.exists(objtex) == 1:
691
objtex = bsys.join(TEXTURES_DIR, baseimgname)
692
if bsys.exists(objtex):
696
img = Image.Load(texfname)
697
# Commented because it's unnecessary:
698
#img.xrep = int(obj.texrep[0])
699
#img.yrep = int(obj.texrep[1])
701
bl_images[obj.tex] = img
703
inform("Couldn't load texture: %s" % baseimgname)
705
missing_textures.append(obj.tex)
706
inform("Couldn't find texture: %s" % baseimgname)
708
for i in range(facesnum):
713
bface = mesh.faces[i]
714
bface.smooth = is_smooth
715
if twoside: bface.mode |= FACE_TWOSIDE
717
bface.mode |= FACE_TEX
719
bface.mat = objmat_indices.index(fmat)
720
fuv = obj.flist_uv[i]
732
mesh.faces[i].uv = fuv
734
# finally, delete the 1st vertex we added to prevent vindices == 0
739
mesh.mode = MESH_AUTOSMOOTH
741
# subdiv: create SUBSURF modifier in Blender
742
if SUBDIV and obj.subdiv > 0:
744
subdiv_render = subdiv
746
if subdiv_render > 6: subdiv_render = 6
747
if subdiv > 3: subdiv = 3
748
modif = object.modifiers.append(Modifier.Types.SUBSURF)
749
modif[Modifier.Settings.LEVELS] = subdiv
750
modif[Modifier.Settings.RENDLEVELS] = subdiv_render
754
self.build_hierarchy()
757
# End of class AC3DImport
759
def filesel_callback(filename):
761
inform("\nTrying to import AC3D model(s) from:\n%s ..." % filename)
763
starttime = bsys.time()
764
test = AC3DImport(filename)
766
endtime = bsys.time() - starttime
767
inform('Done! Data imported in %.3f seconds.\n' % endtime)
771
Window.FileSelector(filesel_callback, "Import AC3D", "*.ac")