first commit

master
joric 2018-01-17 19:27:10 +05:00
commit 544a1976a9
5 changed files with 2683 additions and 0 deletions

51
README.md Normal file
View File

@ -0,0 +1,51 @@
# io_scene_b3d
Blender Import-Export script for Blitz 3D .b3d files
## Installation
The preferred method to install scripts is `File - User preferences - Add-ons - Install Add-on from File`.
Use archived addon (zip archive containing io_scene_b3d directory), and press
`Save User Settings` button afterwards.
You can also copy or symlink the io_scene_b3d directory to the Blender user directory, i.e.:
`%APPDATA%\Blender Foundation\Blender\2.79\scripts\addons\io_scene_b3d`.
## Debugging
Every time you change the script it has to be reloaded with `Reload Scripts` (space bar menu or simply press `F8`).
I've also implemented a debug shortcut `Shift+Ctrl+d`, that reset scene, reloads script and then imports a test file.
## TODO
### Import
* Mind that UV mapping, normals and animation are not yet implemented. Working on it!
* Nodes with multiple meshes get converted into a single mesh (preserving brush_id).
Maybe it's better to split those nodes into separate objects.
* Nodes use original quaternion rotation and it affects user interface.
Maybe it's worth to convert it into euler.
### Export
* Exported files sometimes contain animation keys that go outside the animation.
Assimp doesn't import them so I've added an extra frame, just to be safe.
It's better to recalculate the animation using existing keys.
## License
This is all GPL 2.0. Pull requests welcome.
The import script is a heavily rewriten script from Glogow Poland Mariusz Szkaradek.
I've had to rewrite all the chunk reader stuff and all the import stuff, because Blender API
has heavily changed since then.
The export script uses portions (copied almost verbatim, just ported to Blender Import-Export format)
from supertuxcart project by Diego 'GaNDaLDF' Parisi. Since it's all GPL-licensed, he shouldn't mind.
The b3d format documentation (b3dfile_specs.txt) doesn't have a clear license (I assume Public Domain)
but it was hard to find, so I just put it here in the repository as well.

261
b3dfile_specs.txt Normal file
View File

@ -0,0 +1,261 @@
************************************************************************************
* Blitz3d file format V0.01 *
************************************************************************************
This document and the information contained within is placed in the Public Domain.
Please visit http://www.blitzbasic.co.nz for the latest version of this document.
Please contact marksibly@blitzbasic.co.nz for more information and general inquiries.
************************************************************************************
* Introduction *
************************************************************************************
The Blitz3D file format specifies a format for storing texture, brush and entity descriptions for
use with the Blitz3D programming language.
The rationale behind the creation of this format is to allow for the generation of much richer and
more complex Blitz3D scenes than is possible using established file formats - many of which do not
support key features of Blitz3D, and all of which miss out on at least some features!
A Blitz3D (.b3d) file is split up into a sequence of 'chunks', each of which can contain data
and/or other chunks.
Each chunk is preceded by an eight byte header:
char tag[4] ;4 byte chunk 'tag'
int length ;4 byte chunk length (not including *this* header!)
If a chunk contains both data and other chunks, the data always appears first and is of a fixed
length.
A file parser should ignore unrecognized chunks.
Blitz3D files are stored little endian (intel) style.
Many aspects of the file format are not quite a 'perfect fit' for the way Blitz3D works. This has
been done mainly to keep the file format simple, and to make life easier for the authors of third
party importers/exporters.
************************************************************************************
* Chunk Types *
************************************************************************************
This lists the types of chunks that can appear in a b3d file, and the data they contain.
Color values are always in the range 0 to 1.
string (char[]) values are 'C' style null terminated strings.
Quaternions are used to specify general orientations. The first value is the quaternion 'w' value,
the next 3 are the quaternion 'vector'. A 'null' rotation should be specified as 1,0,0,0.
Anything that is referenced 'by index' always appears EARLIER in the file than anything that
references it.
brush_id references can be -1: no brush.
In the following descriptions, {} is used to signify 'repeating until end of chunk'. Also, a chunk
name enclosed in '[]' signifies the chunk is optional.
Here we go!
BB3D
int version ;file format version: default=1
[TEXS] ;optional textures chunk
[BRUS] ;optional brushes chunk
[NODE] ;optional node chunk
The BB3D chunk appears first in a b3d file, and its length contains the rest of the file.
Version is in major*100+minor format. To check the version, just divide by 100 and compare it with
the major version your software supports, eg:
if file_version/100>my_version/100
RuntimeError "Can't handle this file version!"
EndIf
if file_version Mod 100>my_version Mod 100
;file is a more recent version, but should still be backwardly compatbile with what we can
handle!
EndIf
TEXS
{
char file[] ;texture file name
int flags,blend ;blitz3D TextureFLags and TextureBlend: default=1,2
float x_pos,y_pos ;x and y position of texture: default=0,0
float x_scale,y_scale ;x and y scale of texture: default=1,1
float rotation ;rotation of texture (in radians): default=0
}
The TEXS chunk contains a list of all textures used in the file.
The flags field value can conditional an additional flag value of '65536'. This is used to indicate that the texture uses secondary UV values, ala the TextureCoords command. Yes, I forgot about this one.
BRUS
int n_texs
{
char name[] ;eg "WATER" - just use texture name by default
float red,green,blue,alpha ;Blitz3D Brushcolor and Brushalpha: default=1,1,1,1
float shininess ;Blitz3D BrushShininess: default=0
int blend,fx ;Blitz3D Brushblend and BrushFX: default=1,0
int texture_id[n_texs] ;textures used in brush
}
The BRUS chunk contains a list of all brushes used in the file.
VRTS:
int flags ;1=normal values present, 2=rgba values present
int tex_coord_sets ;texture coords per vertex (eg: 1 for simple U/V) max=8
int tex_coord_set_size ;components per set (eg: 2 for simple U/V) max=4
{
float x,y,z ;always present
float nx,ny,nz ;vertex normal: present if (flags&1)
float red,green,blue,alpha ;vertex color: present if (flags&2)
float tex_coords[tex_coord_sets][tex_coord_set_size] ;tex coords
}
The VRTS chunk contains a list of vertices. The 'flags' value is used to indicate how much extra
data (normal/color) is stored with each vertex, and the tex_coord_sets and tex_coord_set_size
values describe texture coordinate information stored with each vertex.
TRIS:
int brush_id ;brush applied to these TRIs: default=-1
{
int vertex_id[3] ;vertex indices
}
The TRIS chunk contains a list of triangles that all share a common brush.
MESH:
int brush_id ;'master' brush: default=-1
VRTS ;vertices
TRIS[,TRIS...] ;1 or more sets of triangles
The MESH chunk describes a mesh. A mesh only has one VRTS chunk, but potentially many TRIS chunks.
BONE:
{
int vertex_id ;vertex affected by this bone
float weight ;how much the vertex is affected
}
The BONE chunk describes a bone. Weights are applied to the mesh described in the enclosing ANIM -
in 99% of cases, this will simply be the MESH contained in the root NODE chunk.
KEYS:
int flags ;1=position, 2=scale, 4=rotation
{
int frame ;where key occurs
float position[3] ;present if (flags&1)
float scale[3] ;present if (flags&2)
float rotation[4] ;present if (flags&4)
}
The KEYS chunk is a list of animation keys. The 'flags' value describes what kind of animation
info is stored in the chunk - position, scale, rotation, or any combination of.
ANIM:
int flags ;unused: default=0
int frames ;how many frames in anim
float fps ;default=60
The ANIM chunk describes an animation.
NODE:
char name[] ;name of node
float position[3] ;local...
float scale[3] ;coord...
float rotation[4] ;system...
[MESH|BONE] ;what 'kind' of node this is - if unrecognized, just use a Blitz3D
pivot.
[KEYS[,KEYS...]] ;optional animation keys
[NODE[,NODE...]] ;optional child nodes
[ANIM] ;optional animation
The NODE chunk describes a Blitz3D Entity. The scene hierarchy is expressed by the nesting of NODE
chunks.
NODE kinds are currently mutually exclusive - ie: a node can be a MESH, or a BONE, but not both!
However, it can be neither...if no kind is specified, the node is just a 'null' node - in Blitz3D
speak, a pivot.
The presence of an ANIM chunk in a NODE indicates that an animation starts here in the hierarchy.
This allows animations of differing speeds/lengths to be potentially nested.
There are many more 'kind' chunks coming, including camera, light, sprite, plane etc. For now, the
use of a Pivot in cases where the node kind is unknown will allow for backward compatibility.
************************************************************************************
* Examples *
************************************************************************************
A typical b3d file will contain 1 TEXS chunk, 1 BRUS chunk and 1 NODE chunk, like this:
BB3D
1
TEXS
...list of textures...
BRUS
...list of brushes...
NODE
...stuff in the node...
A simple, non-animating, non-textured etc mesh might look like this:
BB3D
1 ;version
NODE
"root_node" ;node name
0,0,0 ;position
1,1,1 ;scale
1,0,0,0 ;rotation
MESH ;the mesh
-1 ;brush: no brush
VRTS ;vertices in the mesh
0 ;no normal/color info in verts
0,0 ;no texture coords in verts
{x,y,z...} ;vertex coordinates
TRIS ;triangles in the mesh
-1 ;no brush for this triangle
{v0,v1,v2...} ;vertices
A more complex 'skinned mesh' might look like this (only chunks shown):
BB3D
TEXS ;texture list
BRUS ;brush list
NODE ;root node
MESH ;mesh - the 'skin'
ANIM ;anim
NODE ;first child of root node - eg: "pelvis"
BONE ;vertex weights for pelvis
KEYS ;anim keys for pelvis
NODE ;first child of pelvis - eg: "left-thigh"
BONE ;bone
KEYS ;anim keys for left-thigh
NODE ;second child of pelvis - eg: "right-thigh"
BONE ;vertex weights for right-thigh
KEYS ;anim keys for right-thigh
...and so on.

203
io_scene_b3d/__init__.py Normal file
View File

@ -0,0 +1,203 @@
# ##### 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-80 compliant>
bl_info = {
"name": "Blitz 3D format",
"author": "Joric",
"blender": (2, 74, 0),
"location": "File > Import-Export",
"description": "Import-Export B3D, meshes, uvs, materials, textures, "
"cameras & lamps",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Import-Export/Blitz3D_B3D",
"support": 'OFFICIAL',
"category": "Import-Export"}
if "bpy" in locals():
import importlib
if "import_b3d" in locals():
importlib.reload(import_b3d)
if "export_b3d" in locals():
importlib.reload(export_b3d)
import bpy
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
StringProperty,
)
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
orientation_helper_factory,
axis_conversion,
)
IOB3DOrientationHelper = orientation_helper_factory("IOB3DOrientationHelper", axis_forward='Y', axis_up='Z')
class ImportB3D(bpy.types.Operator, ImportHelper, IOB3DOrientationHelper):
"""Import from B3D file format (.b3d)"""
bl_idname = "import_scene.blitz3d_b3d"
bl_label = 'Import B3D'
bl_options = {'UNDO'}
filename_ext = ".b3d"
filter_glob = StringProperty(default="*.b3d", options={'HIDDEN'})
constrain_size = FloatProperty(
name="Size Constraint",
description="Scale the model by 10 until it reaches the "
"size constraint (0 to disable)",
min=0.0, max=1000.0,
soft_min=0.0, soft_max=1000.0,
default=10.0,
)
use_image_search = BoolProperty(
name="Image Search",
description="Search subdirectories for any associated images "
"(Warning, may be slow)",
default=True,
)
use_apply_transform = BoolProperty(
name="Apply Transform",
description="Workaround for object transformations "
"importing incorrectly",
default=True,
)
def execute(self, context):
from . import import_b3d
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
return import_b3d.load(self, context, **keywords)
class ExportB3D(bpy.types.Operator, ExportHelper, IOB3DOrientationHelper):
"""Export to B3D file format (.b3d)"""
bl_idname = "export_scene.blitz3d_b3d"
bl_label = 'Export B3D'
filename_ext = ".b3d"
filter_glob = StringProperty(
default="*.b3d",
options={'HIDDEN'},
)
use_selection = BoolProperty(
name="Selection Only",
description="Export selected objects only",
default=False,
)
def execute(self, context):
from . import export_b3d
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
return export_b3d.save(self, context, **keywords)
# Add to a menu
def menu_func_export(self, context):
self.layout.operator(ExportB3D.bl_idname, text="Blitz3D (.b3d)")
def menu_func_import(self, context):
self.layout.operator(ImportB3D.bl_idname, text="Blitz3D (.b3d)")
class DebugMacro(bpy.types.Operator):
bl_idname = "object.debug_macro"
bl_label = "b3d debug"
bl_options = {'REGISTER', 'UNDO'}
filepath = bpy.props.StringProperty(name="filepath",
default='D:\\Projects\\github\\b3d_import\\io_scene_b3d\\flag.b3d')
def execute(self, context):
from . import import_b3d
import sys,imp
# clear scene
for object_ in bpy.context.screen.scene.objects:
bpy.data.objects.remove(object_, True)
module = sys.modules['io_scene_b3d']
imp.reload(module)
import_b3d.load(self, context, filepath=self.filepath)
bpy.ops.view3d.viewnumpad(type='FRONT', align_active=True)
bpy.ops.view3d.view_all(use_all_regions=True, center=True)
if bpy.context.region_data.is_perspective:
bpy.ops.view3d.view_persportho()
return {'FINISHED'}
addon_keymaps = []
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)
# handle the keymap
wm = bpy.context.window_manager
km = wm.keyconfigs.addon.keymaps.new(name='Object Mode', space_type='EMPTY')
kmi = km.keymap_items.new(DebugMacro.bl_idname, 'D', 'PRESS', ctrl=True, shift=True)
addon_keymaps.append((km, kmi))
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)
# handle the keymap
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
if __name__ == "__main__":
register()

1564
io_scene_b3d/export_b3d.py Normal file

File diff suppressed because it is too large Load Diff

604
io_scene_b3d/import_b3d.py Normal file
View File

@ -0,0 +1,604 @@
#!/bin/python3
#2011-04-19 Glogow Poland Mariusz Szkaradek
#Almost completely rewritten by Joric
try:
import bpy
import mathutils
blender = True
except:
blender = False
debug = True
def indent(level):
print(' '*level, end='')
data = {'nodes':[],'version':0,'brushes':[],'textures':[]}
import struct, os
from struct import *
from math import *
def vertexuv():
mesh.vertexUV = 1
for m in range(len(mesh.verts)):
mesh.verts[m].uvco = Vector(uvcoord[m])
mesh.update()
mesh.faceUV = 1
for fc in mesh.faces: fc.uv = [v.uvco for v in fc.verts];fc.smooth = 1
mesh.update()
def drawmesh(name):
global obj,mesh
mesh = bpy.data.meshes.new(name)
mesh.verts.extend(vertexes)
mesh.faces.extend(faceslist,ignoreDups=True)
if len(uvcoord)!=0:
#make_faceuv()
vertexuv()
scene = bpy.data.scenes.active
obj = scene.objects.new(mesh,name)
mesh.recalcNormals()
make_vertex_group()
mesh.update()
def make_vertex_group():
for id_0 in range(len(weighting)):
data = weighting[id_0]
#print data
for id_1 in range(2):
grint = data[0][id_1]
#try:
#gr = fix_groups[gr]
gr = str(grint)
#gr = bonenames[armature_name][gr]
w = data[1][id_1]/100.0
if grint!=-1:
if gr not in mesh.getVertGroupNames():
mesh.addVertGroup(gr)
mesh.update()
mesh.assignVertsToGroup(gr,[id_0],w,1)
#except:
# pass
mesh.update()
def b(n):
return struct.unpack(n*'b', plik.read(n))
def B(n):
return struct.unpack(n*'B', plik.read(n))
def h(n):
return struct.unpack(n*'h', plik.read(n*2))
def H(n):
return struct.unpack(n*'H', plik.read(n*2))
def i(n):
return struct.unpack(n*'i', plik.read(n*4))
def f(n):
return struct.unpack(n*'f', plik.read(n*4))
def word(long):
s=b''
for j in range(0,long):
lit = struct.unpack('c',plik.read(1))[0]
if ord(lit)!=0:
s+=lit
if len(s)>300:
break
return s.decode()
def check_armature():
global armobj,newarm
armobj=None
newarm=None
scn = Scene.GetCurrent()
scene = bpy.data.scenes.active
for object in scene.objects:
if object.getType()=='Armature':
if object.name == 'armature':
scene.objects.unlink(object)
for object in bpy.data.objects:
if object.name == 'armature':
armobj = Blender.Object.Get('armature')
newarm = armobj.getData()
newarm.makeEditable()
for bone in newarm.bones.values():
del newarm.bones[bone.name]
newarm.update()
if armobj==None:
armobj = Blender.Object.New('Armature','armature')
if newarm==None:
newarm = Armature.New('armature')
armobj.link(newarm)
scn.link(armobj)
newarm.drawType = Armature.STICK
armobj.drawMode = Blender.Object.DrawModes.XRAY
for object in scene.objects:
if 'model' in object.name and object.getType()=='Mesh':
armobj.makeParentDeform([object],1,0)
def make_bone():
newarm.makeEditable()
for bone_id in range(len(bonesdata)):
bonedata = bonesdata[bone_id]
bonename = bonedata[0]
eb = Armature.Editbone()
newarm.bones[bonename] = eb
newarm.update()
def make_bone_parent():
newarm.makeEditable()
for bone_id in range(len(bonesdata)):
bonedata = bonesdata[bone_id]
parent_id = bonedata[3]
bonename = bonedata[0]
if parent_id !=-1:
bone = newarm.bones[bonename]
boneparent = newarm.bones[bonesdata[parent_id][0]]
bone.parent = boneparent
newarm.update()
def make_bone_position():
newarm.makeEditable()
for bone_id in range(len(bonesdata)):
bonedata = bonesdata[bone_id]
namebone = bonedata[0]
pos = bonedata[1]
rot = bonedata[2]
qx,qy,qz,qw = rot[0],rot[1],rot[2],rot[3]
rot = Quaternion(qw,qx,qy,qz)
rot = rot.toMatrix().invert()
bone = newarm.bones[namebone]
#if bone.parent:
# bone.head = bone.parent.head+Vector(pos) * bone.parent.matrix
#tempM = rot * bone.parent.matrix
#bone.matrix = tempM
#else:
bone.head = Vector(pos)
bone.matrix = rot
bvec = bone.tail- bone.head
bvec.normalize()
bone.tail = bone.head + 0.01 * bvec
newarm.update()
def skeleton():
check_armature(),make_bone(),make_bone_parent();make_bone_position()
"""
def find_0():
s=b''
while(True):
litera = struct.unpack('c',plik.read(1))[0]
if litera=='\x00':
break
else:
s+=litera
return str(s)
def s3d():
global vertexes,faceslist,weighting,uvcoord
print("Signature:", word(4))
data = H(9)
print("Data:", data)
images = []
materials = []
nTextures = data[2]
print("Textures", nTextures)
for m in range(nTextures):
image_name = find_0().split('.')[0]
images.append(image_name+'.dds')
for file_name in g:
if image_name in file_name and '.dds' in file_name:
print("Texture:", file_name)
if file_name not in Blender.Image.Get():
Blender.Image.Load(dir+os.sep+file_name)
B(9)
nMaterials = data[3]
print("Materials", nMaterials);
for m in range(nMaterials):
material_name = find_0()
materials.append(material_name)
mat = Material.New(material_name)
image_id = H(1)[0]
try:
tex = Texture.New('diff')
tex.setType('Image')
#tex.image = Blender.Image.Get(images[image_id])
tex.image = Blender.Image.Load(dir+os.sep+images[image_id])
mat.setTexture(0,tex,Texture.TexCo.UV,Texture.MapTo.COL)
except:
pass
data1 = H(4)
print("Mat data:", data1)
f(3)
if data1[2] == 1:
print(B(63))
elif data1[2] == 3:
print(B(64))
else:
print(B(55))
nObjects = data[4]
print("Objects", nObjects)
for m in range(nObjects):
print("Find01", find_0())
print("Find01", find_0())
mat_id = H(1)[0]
print("Objects:", B(24), f(4))
B(1)
data2 = H(4)
print(data2)
vertexes = []
faceslist = []
weighting = []
uvcoord = []
for n in range(data2[1]):
back = plik.tell()
#print m,f(3)
vertexes.append(f(3))
f(3)#normals
uvcoord.append([f(1)[0],-f(1)[0]])
plik.seek(back+40)
for n in range(data2[2]):
faceslist.append(H(3))
for n in range(data2[1]):
weighting.append([i(2),B(2)])
#break
#break
drawmesh('model-'+str(m))
mesh.materials+=[Material.Get(materials[mat_id])]
print(plik.tell())
def b3d():
global bonesdata
bonesdata = []
print("File signature:", word(4))
B(5)
nBones = i(1)[0]
print("Bones:", nBones)
for m in range(nBones):
bone_name = find_0()
f(7)
pos = f(3)
rot = f(4)
f(6)
i(2)
parent_id = i(1)[0]
bonesdata.append([bone_name,pos,rot,parent_id])
skeleton()
scene = bpy.data.scenes.active
for object in scene.objects:
if 'model' in object.name and object.getType()=='Mesh':
mesh = object.getData(mesh=1)
for m in range(len(bonesdata)):
print(object.name,m)
if str(m) in mesh.getVertGroupNames():
mesh.renameVertGroup(str(m),bonesdata[m][0])
armobj.makeParentDeform([object],1,0)
print(plik.tell())
"""
def word(long):
s=b''
for j in range(0,long):
lit = struct.unpack('c',plik.read(1))[0]
if ord(lit)!=0:
s+=lit
if len(s)>300:
break
return s.decode()
def find_0():
s=b''
while(True):
litera = struct.unpack('c',plik.read(1))[0]
if litera==b'\x00':
break
else:
s+=litera
return s.decode(errors='ignore')
def next_chunk():
pos = plik.tell()
try:
sig = word(4)
size = i(1)[0]
#if debug: print ("Chunk: %s, pos: %d, size: %d" % (sig, pos, size))
return sig, pos, size, pos+size+8
except struct.error:
#print("EOF")
return '',0,0,0
def parse_node(next, level=0):
name = find_0()
p, s, r = f(3),f(3),f(4)
#if debug: indent(level); print(name)
#print('position/scale/rotation', p,s,r)
node = {'name':name, 'position':p, 'scale':s,'rotation':r,
'meshes':[], 'vertices':[]}
while plik.tell()<next:
sig, pos, size, nextc = next_chunk()
if sig=='ANIM':
flags, frames = i(2)
fps = f(1)[0]
node['anim'] = {'flags':flags, 'frames':frames, 'fps':fps}
#print('flags: %d, frames: %d, fps %.02f' % (flags, frames, fps))
elif sig=='KEYS':
flags = i(1)[0]
keys = []
while plik.tell()<nextc:
frame = i(1)[0]
p = f(3) if flags&1 else []
s = f(3) if flags&2 else []
r = f(4) if flags&4 else []
keys.append((frame,p,s,r))
key = {'frame':frame}
if len(p): key['position'] = p
if len(r): key['rotation'] = r
if len(s): key['scale'] = s
if 'keys' not in node.keys():
node['keys']=[]
node['keys'].append(key)
#print(keys)
#print('total keys', name, len(keys), keys)
elif sig=='BONE':
bones = []
while plik.tell()<nextc:
vertex_id = i(1)[0]
weight = f(1)[0]
bones.append((vertex_id, weight))
#print(bones)
node['bones'] = bones
elif sig=='MESH':
brush_id = i(1)[0]
sig, pos, size, nextv = next_chunk()
if sig!='VRTS': break
flags, tcs, tcss = i(3)
#print(flags, tcs, tcss)
vertices = []
while plik.tell()<nextv:
v = f(3)
n = f(3) if flags&1 else []
rgba = f[4] if flags&2 else []
tex_coords = f(tcs*tcss)
vertices.append((v,n,rgba,tex_coords))
#print(vertices)
#print('brush_id', brush_id, 'vertices', len(vertices))
node['vertices'] = vertices
while plik.tell()<nextc:
sig, pos, size, nextt = next_chunk()
if sig!='TRIS': break
brush_id = i(1)[0]
indices = []
while plik.tell()<nextt:
vertex_id = i(3)
indices.append(vertex_id)
node['meshes'].append({'brush_id':brush_id, 'indices':indices})
#print('brush_id', brush_id, 'ids', len(ids))
elif sig=='NODE':
if 'nodes' not in node.keys():
node['nodes'] = []
node['nodes'].append(parse_node(nextc, level+1))
plik.seek(nextc)
return(node)
def b3d():
level = 0
data['nodes'] = []
data['brushes'] = []
data['textures'] = []
while True:
sig, pos, size, next = next_chunk()
if sig=='':
break
elif sig=='BB3D':
ver = i(1)[0]
data['version'] = ver
continue
elif sig=='TEXS':
while plik.tell()<next:
name = find_0()
flags, blend = i(2)
pos = f(2)
scale = f(2)
rot = f(1)[0]
data['textures'].append({'name':name,'pos':pos,'scale':scale,'rotation':rot})
#print('Texture', name)
elif sig=='BRUS':
n_texs = i(1)[0]
while plik.tell()<next:
name = find_0()
rgba = f(4)
shine = f(1)[0]
blend, fx = i(2)
texture_ids = i(n_texs)
#print('Brush', name)
data['brushes'].append({'name':name, 'rgba':rgba,'shine':shine,
'blend':blend,'fx':fx,'texture_ids':texture_ids})
elif sig=='NODE':
data['nodes'].append(parse_node(next, level))
plik.seek(next)
def flip(v):
#return v
return ((v[0],v[2],v[1]) if len(v)<4 else (v[0], v[1],v[3],v[2]))
def import_node(node, parent):
objName = node['name']
verts = []
coords = {}
index_tot = 0
faces_indices = []
for v,n,rgba,tex_coords in node['vertices']:
verts.append(flip(v))
for m in node['meshes']:
for i in m['indices']:
faces_indices.append(i)
"""
mesh.vertexUV = 1
for m in range(len(mesh.verts)):
mesh.verts[m].uvco = Vector(uvcoord[m])
mesh.update()
mesh.faceUV = 1
for fc in mesh.faces: fc.uv = [v.uvco for v in fc.verts];fc.smooth = 1
mesh.update()
"""
mesh = bpy.data.meshes.new(objName)
mesh.from_pydata(verts, [], faces_indices)
uv_tex = mesh.uv_textures.new(name=objName)
uv_lay = mesh.uv_layers[-1]
blen_data = uv_lay.data
print('len', len(blen_data), 'vs', len(verts))
#
# blen_uvs = me.uv_layers[0]
# for face_uvidx, lidx in zip(face_vert_tex_indices, blen_poly.loop_indices):
# blen_uvs.data[lidx].uv = verts_tex[0 if (face_uvidx is ...) else face_uvidx]
# blen_uvs = mesh.uv_layers[0]
ob = bpy.data.objects.new(objName, mesh)
#mesh.calc_normals() # does not work
if parent:
ob.parent = parent
pos = node['position']
rot = node['rotation']
scale = node['scale']
ob.rotation_mode='QUATERNION'
ob.rotation_quaternion = flip(rot)
ob.scale = flip(scale)
ob.location = flip(pos)
ctx.scene.objects.link(ob)
return ob
def parse_nodes(nodes, level=0, parent=None):
for node in nodes:
ob = None
if debug:
keys = '' if 'keys' not in node.keys() else '\tkeys: '+str(len(node['keys']))
fr = '\trot: '+','.join(['%.2f' % x for x in node['rotation']])
fp = '\tpos: '+','.join(['%.2f' % x for x in node['position']])
v = '\tvertices:'+str(len(node['vertices']))
m = '\tmeshes:'+str(len(node['meshes']))
indent(level)
print(node['name'], fr, fp, keys, v, m)
if blender:
ob = import_node(node, parent)
if 'nodes' in node.keys():
parse_nodes(node['nodes'], level+1, ob)
def load_b3d(filepath,
context,
IMPORT_CONSTRAIN_BOUNDS=10.0,
IMAGE_SEARCH=True,
APPLY_MATRIX=True,
global_matrix=None):
global plik,g,dir,ctx,data
print('Loading', filepath)
plik = open(filepath,'rb')
file = os.path.basename(filepath)
dir = os.path.dirname(filepath)
g = os.listdir(dir)
b3d()
ctx = context
parse_nodes(data['nodes'])
def load(operator,
context,
filepath="",
constrain_size=0.0,
use_image_search=True,
use_apply_transform=True,
global_matrix=None,
):
load_b3d(filepath,
context,
IMPORT_CONSTRAIN_BOUNDS=constrain_size,
IMAGE_SEARCH=use_image_search,
APPLY_MATRIX=use_apply_transform,
global_matrix=global_matrix,
)
return {'FINISHED'}
def import_b3d(filepath):
global plik,g,dir,data
plik = open(filepath,'rb')
b3d()
import json
print(json.dumps(data,separators=(',',':'),indent=1))
parse_nodes(data['nodes'])
if __name__ == "__main__":
if not blender:
#import_b3d('attack.b3d')
#import_b3d('model.b3d')
#import_b3d('untitled.b3d')
import_b3d('../archive/ded.b3d')
#import_b3d('bumerang.b3d')
#import_b3d('../archive/jumper.b3d')
#import_b3d('flag.b3d')