first commit
commit
544a1976a9
|
@ -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.
|
||||
|
|
@ -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.
|
|
@ -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()
|
File diff suppressed because it is too large
Load Diff
|
@ -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')
|
||||
|
Loading…
Reference in New Issue