ported to blender 2.8, pushed to version 1.0
parent
e69bef90df
commit
c648ca286a
41
README.md
41
README.md
|
@ -2,52 +2,39 @@
|
|||
|
||||
Blender Import-Export script for Blitz 3D .b3d files
|
||||
|
||||
## Download
|
||||
|
||||
You may download plugin zip in the [releases](https://github.com/joric/io_scene_b3d/releases) section
|
||||
|
||||
## Installation
|
||||
|
||||
* Userspace method: click "File" - "User Preferences" - "Add-ons" - "Install Add-on from File".
|
||||
The add-on zip file should contain io_scene_b3d directory, including the directory itself.
|
||||
* Alternative method: copy or symlink the io_scene_b3d directory to blender user directory, e.g. to
|
||||
%APPDATA%\Blender Foundation\Blender\2.79\scripts\addons\io_scene_b3d.
|
||||
* Search and enable add-on in "User Preferences" - "Add-ons". Click "Save User Settings" afterwards.
|
||||
%APPDATA%\Blender Foundation\Blender\2.80\scripts\addons\io_scene_b3d.
|
||||
|
||||
Then enable add-on in "User Preferences" - "Add-ons". Click "Save User Settings" afterwards.
|
||||
|
||||
## Debugging
|
||||
|
||||
* Userspace method: every time you make a change the script has to be reloaded using Reload Scripts command (F8).
|
||||
* Alternative method: my shortcut, Shift+Ctrl+D in Object Mode. It resets scene, reloads the script and imports test file.
|
||||
* Somewhat simpler method (Windows only), an autohotkey script I wrote (see the [autohotkey](https://github.com/joric/io_scene_b3d/tree/autohotkey) branch).
|
||||
* Userspace method: every time you make a change the script has to be reloaded (press F3, search for Reload Scripts).
|
||||
* Alternative method: my shortcut, Shift+Ctrl+F in Object Mode. It resets scene, reloads the script and imports test file.
|
||||
|
||||
## TODO
|
||||
|
||||
### Import
|
||||
|
||||
* Mind that animation is not yet implemented. Working on it!
|
||||
* Animation is not yet implemented in version 1.0. Check master branch for updates.
|
||||
* Nodes use original quaternion rotation that affects user interface.
|
||||
Maybe convert them into euler angles.
|
||||
* Sometimes objects appear joined together in a single mesh (an attempt on hardware instancing, I guess).
|
||||
I'm splitting objects with multiple meshes into separate objects but I can't effectively
|
||||
split large pre-baked mashes. Probably solvable with point cloud matching
|
||||
(considering that objects also can overlap).
|
||||
|
||||
### Export
|
||||
|
||||
* Exported files (script by Diego 'GaNDaLDF' Parisi) 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.
|
||||
UPDATE: could not reproduce, reverted. Will double check later.
|
||||
|
||||
## License
|
||||
|
||||
This is all GPL 2.0. Pull requests welcome.
|
||||
This software is covered by GPL 2.0. Pull requests are 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.
|
||||
* The import script based on a heavily rewriten (new reader) script from Glogow Poland Mariusz Szkaradek.
|
||||
* The export script uses portions of script by Diego 'GaNDaLDF' Parisi (ported to Blender 2.8) under GPL license.
|
||||
* The b3d format documentation (b3dfile_specs.txt) doesn't have a clear license (I assume Public Domain).
|
||||
|
||||
## Alternatives
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
# <pep8-80 compliant>
|
||||
|
||||
bl_info = {
|
||||
"name": "Blitz 3D format",
|
||||
"name": "Blitz 3D format (.b3d)",
|
||||
"author": "Joric",
|
||||
"blender": (2, 74, 0),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "Import-Export B3D, meshes, uvs, materials, textures, "
|
||||
"cameras & lamps",
|
||||
|
@ -49,24 +49,22 @@ from bpy.props import (
|
|||
from bpy_extras.io_utils import (
|
||||
ImportHelper,
|
||||
ExportHelper,
|
||||
orientation_helper_factory,
|
||||
orientation_helper,
|
||||
axis_conversion,
|
||||
)
|
||||
|
||||
|
||||
IOB3DOrientationHelper = orientation_helper_factory("IOB3DOrientationHelper", axis_forward='Y', axis_up='Z')
|
||||
|
||||
|
||||
class ImportB3D(bpy.types.Operator, ImportHelper, IOB3DOrientationHelper):
|
||||
@orientation_helper(axis_forward='Y', axis_up='Z')
|
||||
class ImportB3D(bpy.types.Operator, ImportHelper):
|
||||
"""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'})
|
||||
filter_glob : StringProperty(default="*.b3d", options={'HIDDEN'})
|
||||
|
||||
constrain_size = FloatProperty(
|
||||
constrain_size : FloatProperty(
|
||||
name="Size Constraint",
|
||||
description="Scale the model by 10 until it reaches the "
|
||||
"size constraint (0 to disable)",
|
||||
|
@ -74,13 +72,13 @@ class ImportB3D(bpy.types.Operator, ImportHelper, IOB3DOrientationHelper):
|
|||
soft_min=0.0, soft_max=1000.0,
|
||||
default=10.0,
|
||||
)
|
||||
use_image_search = BoolProperty(
|
||||
use_image_search : BoolProperty(
|
||||
name="Image Search",
|
||||
description="Search subdirectories for any associated images "
|
||||
"(Warning, may be slow)",
|
||||
default=True,
|
||||
)
|
||||
use_apply_transform = BoolProperty(
|
||||
use_apply_transform : BoolProperty(
|
||||
name="Apply Transform",
|
||||
description="Workaround for object transformations "
|
||||
"importing incorrectly",
|
||||
|
@ -103,18 +101,20 @@ class ImportB3D(bpy.types.Operator, ImportHelper, IOB3DOrientationHelper):
|
|||
return import_b3d.load(self, context, **keywords)
|
||||
|
||||
|
||||
class ExportB3D(bpy.types.Operator, ExportHelper, IOB3DOrientationHelper):
|
||||
@orientation_helper(axis_forward='Y', axis_up='Z')
|
||||
class ExportB3D(bpy.types.Operator, ExportHelper):
|
||||
"""Export to B3D file format (.b3d)"""
|
||||
bl_idname = "export_scene.blitz3d_b3d"
|
||||
bl_label = 'Export B3D'
|
||||
|
||||
filename_ext = ".b3d"
|
||||
filter_glob = StringProperty(
|
||||
|
||||
filter_glob: StringProperty(
|
||||
default="*.b3d",
|
||||
options={'HIDDEN'},
|
||||
)
|
||||
|
||||
use_selection = BoolProperty(
|
||||
use_selection: BoolProperty(
|
||||
name="Selection Only",
|
||||
description="Export selected objects only",
|
||||
default=False,
|
||||
|
@ -147,60 +147,77 @@ def menu_func_import(self, context):
|
|||
|
||||
class DebugMacro(bpy.types.Operator):
|
||||
bl_idname = "object.debug_macro"
|
||||
bl_label = "b3d debug"
|
||||
bl_label = "Debug Macro"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
from . import import_b3d
|
||||
from . import export_b3d
|
||||
|
||||
filepath = bpy.props.StringProperty(name="filepath", default=import_b3d.filepath)
|
||||
|
||||
def execute(self, context):
|
||||
def execute(self, context: bpy.context):
|
||||
import sys,imp
|
||||
|
||||
print("b3d, loading", self.filepath)
|
||||
|
||||
for material in bpy.data.materials:
|
||||
bpy.data.materials.remove(material)
|
||||
|
||||
for obj in bpy.context.screen.scene.objects:
|
||||
bpy.data.objects.remove(obj, True)
|
||||
for obj in bpy.context.scene.objects:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
module = sys.modules['io_scene_b3d']
|
||||
imp.reload(module)
|
||||
|
||||
import_b3d.load(self, context, filepath=self.filepath)
|
||||
export_b3d.save(self, context, filepath=self.filepath.replace('.b3d','.exported.b3d'))
|
||||
|
||||
"""
|
||||
bpy.ops.view3d.viewnumpad(type='FRONT', align_active=False)
|
||||
|
||||
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__)
|
||||
classes = (
|
||||
ImportB3D,
|
||||
ExportB3D,
|
||||
DebugMacro
|
||||
)
|
||||
|
||||
bpy.types.INFO_MT_file_import.append(menu_func_import)
|
||||
bpy.types.INFO_MT_file_export.append(menu_func_export)
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
|
||||
bpy.types.TOPBAR_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))
|
||||
if wm.keyconfigs.addon:
|
||||
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type='EMPTY')
|
||||
kmi = km.keymap_items.new(DebugMacro.bl_idname, 'F', 'PRESS', ctrl=True, shift=True)
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
bpy.types.INFO_MT_file_import.remove(menu_func_import)
|
||||
bpy.types.INFO_MT_file_export.remove(menu_func_export)
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
|
||||
# handle the keymap
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
del addon_keymaps[:]
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -2,20 +2,21 @@
|
|||
|
||||
"""
|
||||
Name: 'B3D Exporter (.b3d)...'
|
||||
Blender: 259
|
||||
Blender: 280
|
||||
Group: 'Export'
|
||||
Tooltip: 'Export to Blitz3D file format (.b3d)'
|
||||
"""
|
||||
__author__ = ["iego 'GaNDaLDF' Parisi, MTLZ (is06), Joerg Henrichs, Marianne Gagnon"]
|
||||
__url__ = ["www.gandaldf.com"]
|
||||
__version__ = "3.0"
|
||||
__author__ = ["Diego 'GaNDaLDF' Parisi, MTLZ (is06), Joerg Henrichs, Marianne Gagnon, Joric"]
|
||||
__url__ = ["https://github.com/joric/io_scene_b3d"]
|
||||
__version__ = "3.2"
|
||||
__bpydoc__ = """\
|
||||
"""
|
||||
|
||||
# BLITZ3D EXPORTER 3.0
|
||||
# BLITZ3D EXPORTER 3.2
|
||||
# Copyright (C) 2009 by Diego "GaNDaLDF" Parisi - www.gandaldf.com
|
||||
# Lightmap issue fixed by Capricorn 76 Pty. Ltd. - www.capricorn76.com
|
||||
# Blender 2.63 compatiblity based on work by MTLZ, www.is06.com
|
||||
# Blender 2.80 compatibility by Joric
|
||||
# With changes by Marianne Gagnon and Joerg Henrichs, supertuxkart.sf.net (Copyright (C) 2011-2012)
|
||||
#
|
||||
# LICENSE:
|
||||
|
@ -36,9 +37,9 @@ __bpydoc__ = """\
|
|||
bl_info = {
|
||||
"name": "B3D (BLITZ3D) Model Exporter",
|
||||
"description": "Exports a blender scene or object to the B3D (BLITZ3D) format",
|
||||
"author": "Diego 'GaNDaLDF' Parisi, MTLZ (is06), Joerg Henrichs, Marianne Gagnon",
|
||||
"version": (3,1),
|
||||
"blender": (2, 5, 9),
|
||||
"author": "Diego 'GaNDaLDF' Parisi, MTLZ (is06), Joerg Henrichs, Marianne Gagnon, Joric",
|
||||
"version": (3,2),
|
||||
"blender": (2, 8, 0),
|
||||
"api": 31236,
|
||||
"location": "File > Export",
|
||||
"warning": '', # used for warning icon and text in addons panel
|
||||
|
@ -179,17 +180,19 @@ def tesselate_if_needed(objdata):
|
|||
|
||||
def getUVTextures(obj_data):
|
||||
# BMesh in blender 2.63 broke this
|
||||
if bpy.app.version[1] >= 63:
|
||||
return tesselate_if_needed(obj_data).tessface_uv_textures
|
||||
else:
|
||||
return obj_data.uv_textures
|
||||
#if bpy.app.version[1] >= 63:
|
||||
# return tesselate_if_needed(obj_data).tessface_uv_textures #2.8 breaks this
|
||||
#else:
|
||||
# return obj_data.uv_textures
|
||||
return obj_data.uv_layers
|
||||
|
||||
def getFaces(obj_data):
|
||||
# BMesh in blender 2.63 broke this
|
||||
if bpy.app.version[1] >= 63:
|
||||
return tesselate_if_needed(obj_data).tessfaces
|
||||
else:
|
||||
return obj_data.faces
|
||||
#if bpy.app.version[1] >= 63:
|
||||
# return tesselate_if_needed(obj_data).tessfaces
|
||||
#else:
|
||||
# return obj_data.faces
|
||||
return obj_data.polygons
|
||||
|
||||
def getVertexColors(obj_data):
|
||||
# BMesh in blender 2.63 broke this
|
||||
|
@ -198,6 +201,16 @@ def getVertexColors(obj_data):
|
|||
else:
|
||||
return obj_data.vertex_colors
|
||||
|
||||
def getFaceImage(face):
|
||||
try:
|
||||
material = obj.data.materials[face.material_index]
|
||||
texImage = material.node_tree.nodes["Image Texture"]
|
||||
return texImage.image
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# ==== Write TEXS Chunk ====
|
||||
def write_texs(objects=[]):
|
||||
global b3d_parameters
|
||||
|
@ -297,11 +310,9 @@ def write_texs(objects=[]):
|
|||
#data.activeUVLayer = uvlayer
|
||||
|
||||
#if DEBUG: print("<uv face=", face.index, ">")
|
||||
|
||||
img = getUVTextures(data)[iuvlayer].data[face.index].image
|
||||
|
||||
|
||||
img = getFaceImage(face)
|
||||
if img:
|
||||
|
||||
if img.filepath in trimmed_paths:
|
||||
img_name = trimmed_paths[img.filepath]
|
||||
else:
|
||||
|
@ -389,12 +400,12 @@ def write_brus(objects=[]):
|
|||
|
||||
if face.index >= len(uv_textures[iuvlayer].data):
|
||||
continue
|
||||
|
||||
img = uv_textures[iuvlayer].data[face.index].image
|
||||
|
||||
|
||||
img = getFaceImage(face)
|
||||
|
||||
if not img:
|
||||
continue
|
||||
|
||||
|
||||
img_found = 1
|
||||
|
||||
if img.filepath in trimmed_paths:
|
||||
|
@ -424,7 +435,7 @@ def write_brus(objects=[]):
|
|||
mat_colr = mat_data.diffuse_color[0]
|
||||
mat_colg = mat_data.diffuse_color[1]
|
||||
mat_colb = mat_data.diffuse_color[2]
|
||||
mat_alpha = mat_data.alpha
|
||||
mat_alpha = 1.0 # mat_data.alpha # 2.8 fail!
|
||||
mat_name = mat_data.name
|
||||
|
||||
if not mat_name in brus_stack:
|
||||
|
@ -618,7 +629,7 @@ def write_node(objects=[]):
|
|||
matrix = TRANS_MATRIX.copy()
|
||||
scale_matrix = mathutils.Matrix()
|
||||
else:
|
||||
matrix = obj.matrix_world*TRANS_MATRIX
|
||||
matrix = obj.matrix_world @ TRANS_MATRIX
|
||||
scale_matrix = obj.matrix_world.copy()
|
||||
|
||||
|
||||
|
@ -690,10 +701,10 @@ def write_node(objects=[]):
|
|||
#print(" [%.2f %.2f %.2f %.2f]" % (b[2][0], b[2][1], b[2][2], b[2][3]))
|
||||
#print(" [%.2f %.2f %.2f %.2f]" % (b[3][0], b[3][1], b[3][2], b[3][3]))
|
||||
|
||||
par_matrix = b * a
|
||||
par_matrix = b @ a
|
||||
|
||||
transform = mathutils.Matrix([[1,0,0,0],[0,0,-1,0],[0,-1,0,0],[0,0,0,1]])
|
||||
par_matrix = transform*par_matrix*transform
|
||||
par_matrix = transform @ par_matrix @ transform
|
||||
|
||||
# FIXME: that's ugly, find a clean way to change the matrix.....
|
||||
if bpy.app.version[1] >= 62:
|
||||
|
@ -717,7 +728,7 @@ def write_node(objects=[]):
|
|||
#print("==== "+bone.name+" ====")
|
||||
#print("Without parent")
|
||||
|
||||
m = arm_matrix*bone.matrix_local
|
||||
m = arm_matrix @ bone.matrix_local
|
||||
|
||||
#c = arm.matrix_world
|
||||
#print("A : [%.3f %.3f %.3f %.3f]" % (c[0][0], c[0][1], c[0][2], c[0][3]))
|
||||
|
@ -731,7 +742,7 @@ def write_node(objects=[]):
|
|||
#print(" [%.3f %.3f %.3f %.3f]" % (c[2][0], c[2][1], c[2][2], c[2][3]))
|
||||
#print(" [%.3f %.3f %.3f %.3f]" % (c[3][0], c[3][1], c[3][2], c[3][3]))
|
||||
|
||||
par_matrix = m*mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||||
par_matrix = m @ mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||||
|
||||
#c = par_matrix
|
||||
#print("C : [%.3f %.3f %.3f %.3f]" % (c[0][0], c[0][1], c[0][2], c[0][3]))
|
||||
|
@ -763,7 +774,7 @@ def write_node(objects=[]):
|
|||
arm_matrix = arm.matrix_world
|
||||
|
||||
transform = mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||||
arm_matrix = transform*arm_matrix
|
||||
arm_matrix = transform @ arm_matrix
|
||||
|
||||
for bone_name in arm.data.bones.keys():
|
||||
#bone_matrix = mathutils.Matrix(arm_pose.bones[bone_name].poseMatrix)
|
||||
|
@ -801,7 +812,7 @@ def write_node(objects=[]):
|
|||
# if has parent
|
||||
if bone[BONE_PARENT]:
|
||||
par_matrix = mathutils.Matrix(arm_pose.bones[bone[BONE_PARENT].name].matrix)
|
||||
bone_matrix = par_matrix.inverted()*bone_matrix
|
||||
bone_matrix = par_matrix.inverted() @ bone_matrix
|
||||
else:
|
||||
if b3d_parameters.get("local-space"):
|
||||
bone_matrix = bone_matrix*mathutils.Matrix([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])
|
||||
|
@ -812,7 +823,7 @@ def write_node(objects=[]):
|
|||
# print("arm_matrix = ", arm_matrix)
|
||||
# print("bone_matrix = ", bone_matrix)
|
||||
|
||||
bone_matrix = arm_matrix*bone_matrix
|
||||
bone_matrix = arm_matrix @ bone_matrix
|
||||
|
||||
#if frame_count == 1:
|
||||
# print("arm_matrix*bone_matrix", bone_matrix)
|
||||
|
@ -992,7 +1003,7 @@ def write_node_mesh(obj,obj_count,arm_action,exp_root):
|
|||
if arm_action:
|
||||
data = obj.data
|
||||
else:
|
||||
data = obj.to_mesh(the_scene, True, 'PREVIEW')
|
||||
data = obj.to_mesh()
|
||||
|
||||
temp_buf += write_int(-1) #Brush ID
|
||||
temp_buf += write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root) #NODE MESH VRTS
|
||||
|
@ -1063,7 +1074,23 @@ def write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root):
|
|||
mesh_matrix = obj.matrix_world.copy()
|
||||
|
||||
#import time
|
||||
|
||||
|
||||
# new! 2.8 let's precalculate loop indices for every face and vertex id
|
||||
me = data
|
||||
|
||||
my_uvs = {}
|
||||
|
||||
for f in me.polygons:
|
||||
|
||||
my_uvs[f.index] = []
|
||||
|
||||
for i in f.loop_indices:
|
||||
l = me.loops[i]
|
||||
v = me.vertices[l.vertex_index]
|
||||
for j,ul in enumerate(me.uv_layers):
|
||||
uv = ul.data[l.index].uv
|
||||
my_uvs[f.index].append(uv)
|
||||
|
||||
uv_layers_count = len(getUVTextures(data))
|
||||
for face in getFaces(data):
|
||||
|
||||
|
@ -1084,12 +1111,12 @@ def write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root):
|
|||
#a = time.time()
|
||||
|
||||
if arm_action:
|
||||
v = mesh_matrix * data.vertices[vert].co
|
||||
v = mesh_matrix @ data.vertices[vert].co
|
||||
vert_matrix = mathutils.Matrix.Translation(v)
|
||||
else:
|
||||
vert_matrix = mathutils.Matrix.Translation(data.vertices[vert].co)
|
||||
|
||||
vert_matrix *= TRANS_MATRIX
|
||||
vert_matrix @= TRANS_MATRIX
|
||||
vcoord = vert_matrix.to_translation()
|
||||
|
||||
temp_buf.append(write_float_triplet(vcoord.x, vcoord.z, vcoord.y))
|
||||
|
@ -1101,9 +1128,9 @@ def write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root):
|
|||
norm_matrix = mathutils.Matrix.Translation(data.vertices[vert].normal)
|
||||
|
||||
if arm_action:
|
||||
norm_matrix *= mesh_matrix
|
||||
norm_matrix @= mesh_matrix
|
||||
|
||||
norm_matrix *= TRANS_MATRIX
|
||||
norm_matrix @= TRANS_MATRIX
|
||||
normal_vector = norm_matrix.to_translation()
|
||||
|
||||
temp_buf.append(write_float_triplet(normal_vector.x, #NX
|
||||
|
@ -1139,10 +1166,23 @@ def write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root):
|
|||
except:
|
||||
pass
|
||||
vertex_groups[ivert][vg.name] = w
|
||||
|
||||
|
||||
|
||||
# NEW! 2.8 code to write uv from face and vertex_id
|
||||
# vertex_id, vert is in enumerate (face.vertices)
|
||||
# face is from data.polygons
|
||||
# uv_layers_count is from data.uv_layers
|
||||
|
||||
|
||||
for iuvlayer in range(uv_layers_count):
|
||||
uv = my_uvs[face.index][vertex_id]
|
||||
temp_buf.append(write_float_couple(uv[0], 1-uv[1]) )
|
||||
|
||||
|
||||
#e = time.time()
|
||||
#time_in_b2 += e - d
|
||||
|
||||
|
||||
"""
|
||||
# ==== !!bottleneck here!! (40% of the function)
|
||||
if vertex_id == 0:
|
||||
for iuvlayer in range(uv_layers_count):
|
||||
|
@ -1160,6 +1200,7 @@ def write_node_mesh_vrts(obj, data, obj_count, arm_action, exp_root):
|
|||
for iuvlayer in range(uv_layers_count):
|
||||
uv = getUVTextures(data)[iuvlayer].data[face.index].uv4
|
||||
temp_buf.append(write_float_couple(uv[0], 1-uv[1]) ) # U, V
|
||||
"""
|
||||
|
||||
#f = time.time()
|
||||
#time_in_b3 += f - e
|
||||
|
@ -1209,12 +1250,12 @@ def write_node_mesh_tris(obj, data, obj_count,arm_action,exp_root):
|
|||
|
||||
if iuvlayer >= uv_layer_count:
|
||||
continue
|
||||
|
||||
|
||||
img_id = -1
|
||||
|
||||
img = uv_textures[iuvlayer].data[face.index].image
|
||||
|
||||
img = getFaceImage(face)
|
||||
|
||||
if img:
|
||||
|
||||
if img.filepath in trimmed_paths:
|
||||
img_name = trimmed_paths[img.filepath]
|
||||
else:
|
|
@ -22,26 +22,25 @@ def flip(v):
|
|||
def flip_all(v):
|
||||
return [y for y in [flip(x) for x in v]]
|
||||
|
||||
armatures = []
|
||||
bonesdata = []
|
||||
material_mapping = {}
|
||||
weighting = {}
|
||||
bones_ids = {}
|
||||
bones_node = None
|
||||
|
||||
"""
|
||||
def make_skeleton(node):
|
||||
|
||||
objName = 'armature'
|
||||
a = bpy.data.objects.new(objName, bpy.data.armatures.new(objName))
|
||||
|
||||
armatures.append(a);
|
||||
ctx.scene.objects.link(a)
|
||||
ctx.scene.collection.objects.link(a)
|
||||
|
||||
for i in bpy.context.selected_objects: i.select = False #deselect all objects
|
||||
for i in bpy.context.selected_objects: i.select_set(state=False)
|
||||
|
||||
a.select = True
|
||||
a.show_x_ray = True
|
||||
a.data.draw_type = 'STICK'
|
||||
bpy.context.scene.objects.active = a
|
||||
a.select_set(state=True)
|
||||
a.show_in_front = True
|
||||
a.data.display_type = 'STICK'
|
||||
|
||||
bpy.context.view_layer.objects.active = a
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT',toggle=False)
|
||||
|
||||
|
@ -68,11 +67,15 @@ def make_skeleton(node):
|
|||
|
||||
# delete all objects with the same names as the bones
|
||||
for name, pos, rot, parent_id in bonesdata:
|
||||
bpy.data.objects.remove(bpy.data.objects[name])
|
||||
try:
|
||||
bpy.data.objects.remove(bpy.data.objects[name])
|
||||
except:
|
||||
pass
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
for i in bpy.context.selected_objects: i.select = False #deselect all objects
|
||||
#for i in bpy.context.selected_objects: i.select = False #deselect all objects
|
||||
for i in bpy.context.selected_objects: i.select_set(state=False) #deselect all objects #2.8 fails
|
||||
|
||||
# get parent mesh (hardcoded so far)
|
||||
objName = 'anim'
|
||||
|
@ -87,7 +90,7 @@ def make_skeleton(node):
|
|||
|
||||
# create vertex groups
|
||||
for bone in a.data.bones.values():
|
||||
group = ob.vertex_groups.new(bone.name)
|
||||
group = ob.vertex_groups.new(name=bone.name)
|
||||
if bone.name in weighting.keys():
|
||||
for vertex_id, weight in weighting[bone.name]:
|
||||
#vertex_id = remaps[objName][vertex_id]
|
||||
|
@ -112,15 +115,21 @@ def make_skeleton(node):
|
|||
bpy.context.scene.frame_end = node.frames - 1
|
||||
|
||||
|
||||
"""
|
||||
## ANIMATION!
|
||||
bone_string = 'Bip01'
|
||||
bone = {'name' : bone_string}
|
||||
|
||||
curvesLoc = None
|
||||
curvesRot = None
|
||||
bone_string = "pose.bones[\"{}\"].".format(bone.name)
|
||||
group = action.groups.new(name=bone.name)
|
||||
group = action.groups.new(name=bone_string)
|
||||
|
||||
for bone_id, (name, keys, rot, parent_id) in enumerate(bonesdata):
|
||||
for frame in range(node.frames):
|
||||
# (unoptimized) walk through all keys and select the frame
|
||||
for key in keys:
|
||||
if key.frame==frame:
|
||||
pass
|
||||
#print(name, key)
|
||||
for keyframe in range(node.frames):
|
||||
if curvesLoc and curvesRot: break
|
||||
if keyframe.pos and not curvesLoc:
|
||||
|
@ -158,44 +167,11 @@ def make_skeleton(node):
|
|||
curvesRot[i].keyframe_points[-1].co = [keyframe.frame, bone.rotation_quaternion[i]]
|
||||
|
||||
#curve = action.fcurves.new(data_path=bone_string + "rotation_quaternion",index=i)
|
||||
"""
|
||||
"""
|
||||
|
||||
def import_mesh(node, parent):
|
||||
global material_mapping
|
||||
|
||||
def assign_material_slots(ob, node, mat_slots):
|
||||
bpy.context.scene.objects.active = ob
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
me = ob.data
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
bm.faces.ensure_lookup_table()
|
||||
start = 0
|
||||
for face in node.faces:
|
||||
numfaces = len(face.indices)
|
||||
for i in range(numfaces):
|
||||
bm.faces[start+i].material_index = mat_slots[face.brush_id]
|
||||
start += numfaces
|
||||
bmesh.update_edit_mesh(me, True)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
def postprocess(ob, mesh, node):
|
||||
ops = bpy.ops
|
||||
bpy.context.scene.objects.active = ob
|
||||
ops.object.mode_set(mode='EDIT')
|
||||
ops.mesh.select_all(action='SELECT')
|
||||
|
||||
ops.mesh.remove_doubles(threshold=0)
|
||||
bpy.ops.mesh.tris_convert_to_quads()
|
||||
|
||||
ops.mesh.select_all(action='DESELECT')
|
||||
ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# smooth normals
|
||||
mesh.use_auto_smooth = True
|
||||
mesh.auto_smooth_angle = 3.145926*0.2
|
||||
ops.object.select_all(action="SELECT")
|
||||
ops.object.shade_smooth()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
def import_mesh(node):
|
||||
mesh = bpy.data.meshes.new(node.name)
|
||||
|
||||
# join face arrays
|
||||
|
@ -213,76 +189,121 @@ def import_mesh(node):
|
|||
ob = bpy.data.objects.new(node.name, mesh)
|
||||
|
||||
# assign uv coordinates
|
||||
vert_uvs = [(0,0) if len(uv)==0 else (uv[0], 1-uv[1]) for uv in node.uvs]
|
||||
me = ob.data
|
||||
me.uv_textures.new()
|
||||
me.uv_layers[-1].data.foreach_set("uv", [uv for pair in [vert_uvs[l.vertex_index] for l in me.loops] for uv in pair])
|
||||
bpymesh = ob.data
|
||||
uvs = [(0,0) if len(uv)==0 else (uv[0], 1-uv[1]) for uv in node.uvs]
|
||||
uvlist = [i for poly in bpymesh.polygons for vidx in poly.vertices for i in uvs[vidx]]
|
||||
bpymesh.uv_layers.new().data.foreach_set('uv', uvlist)
|
||||
|
||||
# assign materials and textures
|
||||
mat_slots = {}
|
||||
# adding object materials (insert-ordered)
|
||||
for key, value in material_mapping.items():
|
||||
ob.data.materials.append(bpy.data.materials[value])
|
||||
|
||||
# assign material_indexes
|
||||
poly = 0
|
||||
for face in node.faces:
|
||||
if face.brush_id in materials:
|
||||
mat = materials[face.brush_id]
|
||||
ob.data.materials.append(mat)
|
||||
mat_slots[face.brush_id] = len(ob.data.materials)-1
|
||||
for uv_face in ob.data.uv_textures.active.data:
|
||||
if mat.active_texture:
|
||||
uv_face.image = mat.active_texture.image
|
||||
|
||||
# link object to scene
|
||||
ctx.scene.objects.link(ob)
|
||||
|
||||
if len(node.faces)>1:
|
||||
assign_material_slots(ob, node, mat_slots)
|
||||
|
||||
#postprocess(ob, mesh, node) # breaks weighting
|
||||
for _ in face.indices:
|
||||
ob.data.polygons[poly].material_index = face.brush_id
|
||||
poly += 1
|
||||
|
||||
return ob
|
||||
|
||||
def import_node(node, parent):
|
||||
global armatures, bonesdata, weighting, bones_ids, bones_node
|
||||
def select_recursive(root):
|
||||
for c in root.children:
|
||||
select_recursive(c)
|
||||
root.select_set(state=True)
|
||||
|
||||
def make_armature_recursive(root, a, parent_bone):
|
||||
bone = a.data.edit_bones.new(root.name)
|
||||
v = root.matrix_world.to_translation()
|
||||
bone.tail = v
|
||||
# bone.head = (v[0]-0.01,v[1],v[2]) # large handles!
|
||||
bone.parent = parent_bone
|
||||
if bone.parent:
|
||||
bone.head = bone.parent.tail
|
||||
parent_bone = bone
|
||||
for c in root.children:
|
||||
make_armature_recursive(c, a, parent_bone)
|
||||
|
||||
def make_armatures():
|
||||
global ctx
|
||||
global imported_armatures, weighting
|
||||
|
||||
for dummy_root in imported_armatures:
|
||||
objName = 'armature'
|
||||
a = bpy.data.objects.new(objName, bpy.data.armatures.new(objName))
|
||||
ctx.scene.collection.objects.link(a)
|
||||
for i in bpy.context.selected_objects: i.select_set(state=False)
|
||||
a.select_set(state=True)
|
||||
a.show_in_front = True
|
||||
a.data.display_type = 'OCTAHEDRAL'
|
||||
bpy.context.view_layer.objects.active = a
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT',toggle=False)
|
||||
make_armature_recursive(dummy_root, a, None)
|
||||
bpy.ops.object.mode_set(mode='OBJECT',toggle=False)
|
||||
|
||||
# set ob to mesh object
|
||||
ob = dummy_root.parent
|
||||
a.parent = ob
|
||||
|
||||
# delete dummy objects hierarchy
|
||||
for i in bpy.context.selected_objects: i.select_set(state=False)
|
||||
select_recursive(dummy_root)
|
||||
bpy.ops.object.delete(use_global=True)
|
||||
|
||||
# apply armature modifier
|
||||
modifier = ob.modifiers.new(type="ARMATURE", name="armature")
|
||||
modifier.object = a
|
||||
|
||||
# create vertex groups
|
||||
for bone in a.data.bones.values():
|
||||
group = ob.vertex_groups.new(name=bone.name)
|
||||
if bone.name in weighting.keys():
|
||||
for vertex_id, weight in weighting[bone.name]:
|
||||
group_indices = [vertex_id]
|
||||
group.add(group_indices, weight, 'REPLACE')
|
||||
a.parent.data.update()
|
||||
|
||||
def import_bone(node, parent=None):
|
||||
global imported_armatures, weighting
|
||||
# add dummy objects to calculate bone positions later
|
||||
ob = bpy.data.objects.new(node.name, None)
|
||||
|
||||
# fill weighting map for later use
|
||||
w = []
|
||||
for vert_id, weight in node['bones']:
|
||||
w.append((vert_id, weight))
|
||||
weighting[node.name] = w
|
||||
|
||||
# check parent, add root armature
|
||||
if parent and parent.type=='MESH':
|
||||
imported_armatures.append(ob)
|
||||
|
||||
return ob
|
||||
|
||||
def import_node_recursive(node, parent=None):
|
||||
ob = None
|
||||
|
||||
if 'vertices' in node and 'faces' in node:
|
||||
ob = import_mesh(node)
|
||||
else:
|
||||
ob = import_mesh(node, parent)
|
||||
elif 'bones' in node:
|
||||
ob = import_bone(node, parent)
|
||||
elif node.name:
|
||||
ob = bpy.data.objects.new(node.name, None)
|
||||
ctx.scene.objects.link(ob)
|
||||
|
||||
ob.rotation_mode='QUATERNION'
|
||||
ob.rotation_quaternion = flip(node.rotation)
|
||||
ob.scale = flip(node.scale)
|
||||
ob.location = flip(node.position)
|
||||
if ob:
|
||||
ctx.scene.collection.objects.link(ob)
|
||||
|
||||
if parent:
|
||||
ob.parent = parent
|
||||
|
||||
if 'bones' in node:
|
||||
bone_name = node.name
|
||||
|
||||
# we need numeric parent_id for bonesdata
|
||||
parent_id = -1
|
||||
if parent:
|
||||
if parent.name in bones_ids.keys():
|
||||
parent_id = bones_ids[parent.name]
|
||||
bonesdata.append([bone_name,None,None,parent_id])
|
||||
bones_ids[bone_name] = len(bonesdata)-1
|
||||
ob.parent = parent
|
||||
|
||||
# fill weighting map for later use
|
||||
w = []
|
||||
for vert_id, weight in node['bones']:
|
||||
w.append((vert_id, weight))
|
||||
weighting[bone_name] = w
|
||||
ob.rotation_mode='QUATERNION'
|
||||
ob.rotation_quaternion = flip(node.rotation)
|
||||
ob.scale = flip(node.scale)
|
||||
ob.location = flip(node.position)
|
||||
|
||||
if 'bones' in node and not bones_node:
|
||||
print(bones_node)
|
||||
bones_node = node
|
||||
|
||||
return ob
|
||||
|
||||
def walk(root, parent=None):
|
||||
for node in root.nodes:
|
||||
ob = import_node(node, parent)
|
||||
walk(node, ob)
|
||||
for x in node.nodes:
|
||||
import_node_recursive(x, ob)
|
||||
|
||||
def load_b3d(filepath,
|
||||
context,
|
||||
|
@ -290,44 +311,48 @@ def load_b3d(filepath,
|
|||
IMAGE_SEARCH=True,
|
||||
APPLY_MATRIX=True,
|
||||
global_matrix=None):
|
||||
|
||||
global ctx
|
||||
global material_mapping
|
||||
|
||||
ctx = context
|
||||
data = B3DTree().parse(filepath)
|
||||
|
||||
global images, materials
|
||||
images = {}
|
||||
materials = {}
|
||||
|
||||
# load images
|
||||
images = {}
|
||||
dirname = os.path.dirname(filepath)
|
||||
for i, texture in enumerate(data['textures'] if 'textures' in data else []):
|
||||
texture_name = os.path.basename(texture['name'])
|
||||
for mat in data.materials:
|
||||
if mat.tids[0]==i:
|
||||
images[i] = load_image(texture_name, dirname, check_existing=True,
|
||||
place_holder=False, recursive=IMAGE_SEARCH)
|
||||
images[i] = (texture_name, load_image(texture_name, dirname, check_existing=True,
|
||||
place_holder=False, recursive=IMAGE_SEARCH))
|
||||
|
||||
# create materials
|
||||
material_mapping = {}
|
||||
for i, mat in enumerate(data.materials if 'materials' in data else []):
|
||||
name = mat.name
|
||||
material = bpy.data.materials.new(name)
|
||||
material.diffuse_color = mat.rgba[:-1]
|
||||
material.alpha = mat.rgba[3]
|
||||
material.use_transparency = material.alpha < 1
|
||||
texture = bpy.data.textures.new(name=name, type='IMAGE')
|
||||
tid = mat.tids[0]
|
||||
if tid in images:
|
||||
texture.image = images[tid]
|
||||
mtex = material.texture_slots.add()
|
||||
mtex.texture = texture
|
||||
mtex.texture_coords = 'UV'
|
||||
mtex.use_map_color_diffuse = True
|
||||
materials[i] = material
|
||||
material = bpy.data.materials.new(mat.name)
|
||||
material_mapping[i] = material.name
|
||||
material.diffuse_color = mat.rgba
|
||||
material.blend_method = 'MULTIPLY' if mat.rgba[3] < 1.0 else 'OPAQUE'
|
||||
|
||||
global armatures, bonesdata, weighting, bones_ids, bones_node
|
||||
walk(data)
|
||||
if data.frames:
|
||||
make_skeleton(data)
|
||||
tid = mat.tids[0] if len(mat.tids) else -1
|
||||
|
||||
if tid in images:
|
||||
name, image = images[tid]
|
||||
texture = bpy.data.textures.new(name=name, type='IMAGE')
|
||||
material.use_nodes = True
|
||||
bsdf = material.node_tree.nodes["Principled BSDF"]
|
||||
texImage = material.node_tree.nodes.new('ShaderNodeTexImage')
|
||||
texImage.image = image
|
||||
material.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color'])
|
||||
|
||||
global imported_armatures, weighting
|
||||
imported_armatures = []
|
||||
weighting = {}
|
||||
|
||||
import_node_recursive(data)
|
||||
make_armatures()
|
||||
|
||||
def load(operator,
|
||||
context,
|
||||
|
@ -348,7 +373,9 @@ def load(operator,
|
|||
|
||||
return {'FINISHED'}
|
||||
|
||||
filepath = 'C:/Games/GnomE/media/models/gnome/model.b3d'
|
||||
#filepath = 'D:/Projects/github/io_scene_b3d/testing/gooey.b3d'
|
||||
filepath = 'C:/Games/GnomE/media/models/ded/ded.b3d'
|
||||
#filepath = 'C:/Games/GnomE/media/models/gnome/model.b3d'
|
||||
#filepath = 'C:/Games/GnomE/media/levels/level1.b3d'
|
||||
#filepath = 'C:/Games/GnomE/media/models/gnome/go.b3d'
|
||||
#filepath = 'C:/Games/GnomE/media/models/flag/flag.b3d'
|
||||
|
@ -356,3 +383,4 @@ filepath = 'C:/Games/GnomE/media/models/gnome/model.b3d'
|
|||
if __name__ == "__main__":
|
||||
p = B3DDebugParser()
|
||||
p.parse(filepath)
|
||||
|
Loading…
Reference in New Issue