WIP: new addon - HxA import/export #13
60
io_scene_hxa/__init__.py
Normal file
60
io_scene_hxa/__init__.py
Normal file
@ -0,0 +1,60 @@
|
||||
bl_info = {
|
||||
"name": "HxA asset format",
|
||||
"description": "Import-Export HxA",
|
||||
"author": "SoslanGM (Soslan Guchmazov)",
|
||||
"version": (0, 1),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "File > Import-Export",
|
||||
"warning": "",
|
||||
"doc_url": "https://github.com/SoslanGM/HxApy_Blender_import-export",
|
||||
"tracker_url": "",
|
||||
"support": "TESTING",
|
||||
"category": "Import-Export",
|
||||
}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
|
||||
if "import_hxa_py" in locals():
|
||||
importlib.reload(import_hxa_py)
|
||||
if "export_hxa_py" in locals():
|
||||
importlib.reload(export_hxa_py)
|
||||
|
||||
|
||||
import bpy
|
||||
from . import import_hxa_py
|
||||
from . import export_hxa_py
|
||||
|
||||
|
||||
def menu_func_import(self, context):
|
||||
self.layout.operator(import_hxa_py.ImportHXA.bl_idname, text="HxA (.hxa)")
|
||||
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(export_hxa_py.ExportHXA.bl_idname, text="HxA (.hxa)")
|
||||
|
||||
|
||||
classes = (
|
||||
import_hxa_py.ImportHXA,
|
||||
export_hxa_py.ExportHXA,
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
389
io_scene_hxa/export_hxa_py.py
Normal file
389
io_scene_hxa/export_hxa_py.py
Normal file
@ -0,0 +1,389 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
|
||||
from . import hxapy_header as hxa
|
||||
from . import hxapy_util as hxa_util
|
||||
from . import hxapy_read_write as hxa_rw
|
||||
from . import hxapy_validate as hxa_valid
|
||||
|
||||
from bpy.props import StringProperty
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExportHXA(bpy.types.Operator, ExportHelper):
|
||||
"""Export a mesh as a HxA file"""
|
||||
|
||||
bl_idname = "export_model.hxa"
|
||||
bl_label = "Export HxA"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
filename_ext = ".hxa"
|
||||
filter_glob: StringProperty(default="*.hxa", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
if bpy.ops.object.mode_set.poll():
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
hxa_dict = export_payload()
|
||||
if not hxa_valid.hxa_util_validate(hxa_dict):
|
||||
log.info(f"{self.filepath} couldn't pass validation")
|
||||
self.report({"ERROR"}, f"{self.filepath} couldn't pass validation")
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
f = open(self.filepath, "wb")
|
||||
except OSError:
|
||||
log.info(f"HXA Error: File {self.filepath} could not be open for writing\n")
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"HXA Error: File {self.filepath} could not be open for writing\n",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
hxa_rw.write_hxa(f, hxa_dict)
|
||||
f.close()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def hxa_meta(name, typ, data):
|
||||
m = {"name": name, "type": typ, "data": data}
|
||||
return m
|
||||
|
||||
|
||||
def meta__armature_data(arm_ob, arm):
|
||||
"""
|
||||
Packs all the armature(bones) data into HxA meta fields.
|
||||
"""
|
||||
arm_location = arm_ob.location[:]
|
||||
arm_scale = arm_ob.scale[:]
|
||||
bone_count = len(arm.bones)
|
||||
|
||||
bpy.context.view_layer.objects.active = arm_ob
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
heads = [list(x.head) for x in arm.edit_bones]
|
||||
tails = [list(x.tail) for x in arm.edit_bones]
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
log.debug("Edit bone heads")
|
||||
for h in heads:
|
||||
log.debug(h)
|
||||
log.debug("Edit bone tails")
|
||||
for t in tails:
|
||||
log.debug(t)
|
||||
|
||||
heads = hxa_util.flatten_list(heads)
|
||||
tails = hxa_util.flatten_list(tails)
|
||||
names = [x.name for x in arm.bones]
|
||||
parents = [x.parent.name if x.parent else "" for x in arm.bones]
|
||||
|
||||
meta_armature_data_entries = []
|
||||
meta_armature_data_entries.append(
|
||||
hxa_meta(
|
||||
"meta armature location", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, arm_location
|
||||
)
|
||||
)
|
||||
meta_armature_data_entries.append(
|
||||
hxa_meta("meta armature scale", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, arm_scale)
|
||||
)
|
||||
meta_armature_data_entries.append(
|
||||
hxa_meta("meta bones heads", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, heads)
|
||||
)
|
||||
meta_armature_data_entries.append(
|
||||
hxa_meta("meta bones tails", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, tails)
|
||||
)
|
||||
|
||||
bone_names_entries = [
|
||||
hxa_meta("", hxa.HXAMetaDataType.HXA_MDT_TEXT, names[i])
|
||||
for i in range(bone_count)
|
||||
]
|
||||
meta_armature_data_entries.append(
|
||||
hxa_meta(
|
||||
"meta bones names", hxa.HXAMetaDataType.HXA_MDT_META, bone_names_entries
|
||||
)
|
||||
)
|
||||
|
||||
bone_parents_entries = [
|
||||
hxa_meta("", hxa.HXAMetaDataType.HXA_MDT_TEXT, parents[i])
|
||||
for i in range(bone_count)
|
||||
]
|
||||
meta_armature_data_entries.append(
|
||||
hxa_meta(
|
||||
"meta bones parents", hxa.HXAMetaDataType.HXA_MDT_META, bone_parents_entries
|
||||
)
|
||||
)
|
||||
|
||||
meta_armature_data = hxa_meta(
|
||||
"meta armature data",
|
||||
hxa.HXAMetaDataType.HXA_MDT_META,
|
||||
meta_armature_data_entries,
|
||||
)
|
||||
|
||||
return meta_armature_data
|
||||
|
||||
|
||||
def extract_weights(ob):
|
||||
vgroups = ob.vertex_groups
|
||||
|
||||
indexes_biglist = [[] for _ in vgroups]
|
||||
weights_biglist = [[] for _ in vgroups]
|
||||
for vi, vert in enumerate(ob.data.vertices):
|
||||
for g in vert.groups:
|
||||
indexes_biglist[g.group].append(vi)
|
||||
weights_biglist[g.group].append(g.weight)
|
||||
|
||||
return (indexes_biglist, weights_biglist)
|
||||
|
||||
|
||||
def hxapy_type_meta(typ):
|
||||
"""Which HxA meta type will we use to write this type into the export file?"""
|
||||
if typ == int:
|
||||
return hxa.HXAMetaDataType.HXA_MDT_INT64
|
||||
elif typ == float:
|
||||
return hxa.HXAMetaDataType.HXA_MDT_DOUBLE
|
||||
elif typ == str:
|
||||
return hxa.HXAMetaDataType.HXA_MDT_TEXT
|
||||
|
||||
|
||||
# def ExportPayload(context, filepath):
|
||||
def export_payload():
|
||||
"""
|
||||
The overarching function to produce our dictionary representation of a HxA file,
|
||||
before we write it to disk.
|
||||
"""
|
||||
bm = bmesh.new()
|
||||
ob_mesh = bpy.context.object
|
||||
me = ob_mesh.data
|
||||
bm.from_mesh(me)
|
||||
|
||||
vert_count = len(bm.verts)
|
||||
face_count = len(bm.faces)
|
||||
verts = [[c for c in v.co] for v in bm.verts]
|
||||
faces = [f for f in bm.faces]
|
||||
references = [[v.index for v in f.verts] for f in faces]
|
||||
references = [
|
||||
[-x - 1 if _ref.index(x) == len(_ref) - 1 else x for x in _ref]
|
||||
for _ref in references
|
||||
]
|
||||
|
||||
verts = hxa_util.flatten_list(verts)
|
||||
references = hxa_util.flatten_list(references)
|
||||
log.debug(verts)
|
||||
log.debug(references)
|
||||
|
||||
bm.free()
|
||||
|
||||
hxa_dict = {}
|
||||
hxa_dict["version"] = hxa.HXA_VERSION_FORMAT
|
||||
hxa_dict["node_count"] = 1
|
||||
|
||||
# *** Meta data
|
||||
meta_data = []
|
||||
|
||||
# ** Mesh(meta) data
|
||||
meta_meshdata_entries = []
|
||||
meta_meshdata_entries.append(
|
||||
hxa_meta("meta objectname", hxa.HXAMetaDataType.HXA_MDT_TEXT, ob_mesh.name)
|
||||
)
|
||||
meta_meshdata_entries.append(
|
||||
hxa_meta("meta meshname", hxa.HXAMetaDataType.HXA_MDT_TEXT, me.name)
|
||||
)
|
||||
meta_meshdata_entries.append(
|
||||
hxa_meta(
|
||||
"meta location", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, ob_mesh.location[:]
|
||||
)
|
||||
)
|
||||
meta_meshdata_entries.append(
|
||||
hxa_meta("meta scale", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, ob_mesh.scale[:])
|
||||
)
|
||||
|
||||
meta_data.append(
|
||||
hxa_meta(
|
||||
"meta mesh data", hxa.HXAMetaDataType.HXA_MDT_META, meta_meshdata_entries
|
||||
)
|
||||
)
|
||||
|
||||
# ** Shapekeys
|
||||
if ob_mesh.data.shape_keys:
|
||||
object_shapekeys = ob_mesh.data.shape_keys.key_blocks
|
||||
shapekey_count = len(object_shapekeys)
|
||||
|
||||
meta_shapekeys_data = []
|
||||
for i in range(shapekey_count):
|
||||
name = object_shapekeys[i].name
|
||||
shapekey_values = []
|
||||
for x in object_shapekeys[i].data.values():
|
||||
shapekey_values += [y for y in x.co]
|
||||
|
||||
meta_shapekeys_data.append(
|
||||
hxa_meta(name, hxa.HXAMetaDataType.HXA_MDT_DOUBLE, shapekey_values)
|
||||
)
|
||||
|
||||
meta_data.append(
|
||||
hxa_meta(
|
||||
"meta shapekeys", hxa.HXAMetaDataType.HXA_MDT_META, meta_shapekeys_data
|
||||
)
|
||||
)
|
||||
|
||||
# ** Armature
|
||||
if ob_mesh.parent:
|
||||
if (ob_mesh.parent.type == "ARMATURE") & (
|
||||
type(ob_mesh.parent.data) == bpy.types.Armature
|
||||
):
|
||||
ob_arm = ob_mesh.parent
|
||||
arm = ob_arm.data
|
||||
|
||||
meta_armaturedata = meta__armature_data(ob_arm, arm)
|
||||
meta_data.append(meta_armaturedata)
|
||||
|
||||
# ** Vertex weights
|
||||
if len(meta_data) > 0:
|
||||
indexes_list, weights_list = extract_weights(ob_mesh)
|
||||
|
||||
vgroup_count = len(ob_mesh.vertex_groups)
|
||||
if vgroup_count:
|
||||
# vertex indexes
|
||||
meta_weightindexes_data = [
|
||||
hxa_meta("", hxa.HXAMetaDataType.HXA_MDT_INT64, indexes_list[i])
|
||||
for i in range(vgroup_count)
|
||||
]
|
||||
meta_data.append(
|
||||
hxa_meta(
|
||||
"meta weight indexes",
|
||||
hxa.HXAMetaDataType.HXA_MDT_META,
|
||||
meta_weightindexes_data,
|
||||
)
|
||||
)
|
||||
|
||||
# vertex weights
|
||||
meta_vertexweights_data = [
|
||||
hxa_meta("", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, weights_list[i])
|
||||
for i in range(vgroup_count)
|
||||
]
|
||||
meta_data.append(
|
||||
hxa_meta(
|
||||
"meta vertex weights",
|
||||
hxa.HXAMetaDataType.HXA_MDT_META,
|
||||
meta_vertexweights_data,
|
||||
)
|
||||
)
|
||||
|
||||
# ** creases
|
||||
creases = [x.crease for x in me.edges]
|
||||
edges = [list(e.vertices) for e in me.edges]
|
||||
crease_tuples = []
|
||||
for i in range(len(edges)):
|
||||
crease_tuples.append((edges[i], creases[i]))
|
||||
|
||||
crease_tuples = sorted(crease_tuples, key=lambda t: (t[0][0], t[0][1]))
|
||||
|
||||
log.debug(f"> {edges}")
|
||||
# sorted_edges = sorted(edges, key = lambda x: (x[0], x[1]))
|
||||
sorted_edges, sorted_creases = zip(*crease_tuples)
|
||||
edge_verts = hxa_util.flatten_list(sorted_edges)
|
||||
log.debug(f"Edge verts: {edge_verts}")
|
||||
|
||||
# check for !=0 creases
|
||||
creases_present = len([x != 0 for x in creases]) > 0
|
||||
if creases_present:
|
||||
meta_creases_data_entries = []
|
||||
meta_creases_data_entries.append(
|
||||
hxa_meta("", hxa.HXAMetaDataType.HXA_MDT_INT64, edge_verts)
|
||||
)
|
||||
meta_creases_data_entries.append(
|
||||
hxa_meta("", hxa.HXAMetaDataType.HXA_MDT_DOUBLE, sorted_creases)
|
||||
)
|
||||
|
||||
meta_data.append(
|
||||
hxa_meta(
|
||||
"meta creases",
|
||||
hxa.HXAMetaDataType.HXA_MDT_META,
|
||||
meta_creases_data_entries,
|
||||
)
|
||||
)
|
||||
|
||||
# ** custom props
|
||||
custom_props = list(ob_mesh.keys())
|
||||
if len(custom_props) > 0:
|
||||
meta_customprops_data = []
|
||||
for cp in custom_props:
|
||||
customprop = ob_mesh[cp]
|
||||
import idprop
|
||||
|
||||
if type(customprop) == idprop.types.IDPropertyArray:
|
||||
al = len(customprop)
|
||||
mtype = hxa.HXAMetaDataType(type(customprop[0]))
|
||||
data = list(customprop)
|
||||
else:
|
||||
mtype = hxa.HXAMetaDataType(type(customprop))
|
||||
data = customprop
|
||||
if mtype == hxa.HXA_MDT_TEXT:
|
||||
al = len(data)
|
||||
else:
|
||||
al = 1
|
||||
|
||||
meta_cp_name = cp
|
||||
meta_cp = {
|
||||
"name_length": len(meta_cp_name),
|
||||
"name": meta_cp_name,
|
||||
"type": hxa.HXAMetaDataType(mtype).value,
|
||||
"array_length": al,
|
||||
"data": data,
|
||||
}
|
||||
meta_customprops_data.append(meta_cp)
|
||||
# - I'll do this later. Might not be as straightforward.
|
||||
# meta_customprops_data.append(hxa_meta(meta_cp_name, hxa.HXAMetaType(mtype).value, data))
|
||||
|
||||
meta_data.append(
|
||||
hxa_meta(
|
||||
"meta custom properties",
|
||||
hxa.HXAMetaDataType.HXA_MDT_META,
|
||||
meta_customprops_data,
|
||||
)
|
||||
)
|
||||
|
||||
# *** Mesh(geometry) data
|
||||
vertex_layer = {
|
||||
"name_length": len(hxa.HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_NAME),
|
||||
"name": hxa.HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_NAME,
|
||||
"components": hxa.HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_COMPONENTS,
|
||||
"type": hxa.HXALayerDataType.HXA_LDT_FLOAT,
|
||||
"data": verts,
|
||||
}
|
||||
vert_stack = {"layer_count": 1, "layers": [vertex_layer]}
|
||||
|
||||
reference_layer = {
|
||||
"name_length": len(hxa.HXA_CONVENTION_HARD_BASE_CORNER_LAYER_NAME),
|
||||
"name": hxa.HXA_CONVENTION_HARD_BASE_CORNER_LAYER_NAME,
|
||||
"components": hxa.HXA_CONVENTION_HARD_BASE_CORNER_LAYER_COMPONENTS,
|
||||
"type": hxa.HXA_CONVENTION_HARD_BASE_CORNER_LAYER_TYPE,
|
||||
"data": references,
|
||||
}
|
||||
corner_stack = {"layer_count": 1, "layers": [reference_layer]}
|
||||
|
||||
edge_stack = {"layer_count": 0, "layers": []}
|
||||
|
||||
face_stack = {"layer_count": 0, "layers": []}
|
||||
|
||||
content = {
|
||||
"vertex_count": vert_count,
|
||||
"vertex_stack": vert_stack,
|
||||
"edge_corner_count": len(references),
|
||||
"corner_stack": corner_stack,
|
||||
"edge_stack": edge_stack,
|
||||
"face_count": face_count,
|
||||
"face_stack": face_stack,
|
||||
}
|
||||
node = {
|
||||
"type": hxa.HXANodeType.HXA_NT_GEOMETRY,
|
||||
"meta_data_count": len(meta_data),
|
||||
"meta_data": meta_data,
|
||||
"content": content,
|
||||
}
|
||||
hxa_dict["nodes"] = [node]
|
||||
return hxa_dict
|
88
io_scene_hxa/hxapy_header.py
Normal file
88
io_scene_hxa/hxapy_header.py
Normal file
@ -0,0 +1,88 @@
|
||||
# This is a Python version of the original HxA header, written by Soslan Guchmazov (@SoslanGM).
|
||||
# The original HxA header, as well as the HxA format itself, both for C programming language,
|
||||
# was developed by Eskil Steenberg (@quelsolaar).
|
||||
# You can find the original HxA readme at https://github.com/quelsolaar/HxA#readme,
|
||||
# and the repository with this header(as well as source code for my Import/Export addon for Blender)
|
||||
# is at https://github.com/SoslanGM/HxApy_Blender_import-export
|
||||
|
||||
# Big thanks to @Scurest for help with the feedback, suggestions and fixes.
|
||||
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
HXA_VERSION_API = "0.3"
|
||||
HXA_VERSION_FORMAT = 3
|
||||
|
||||
|
||||
HXA_NAME_MAX_LENGTH = 255
|
||||
|
||||
|
||||
class HXANodeType(IntEnum):
|
||||
HXA_NT_META_ONLY = 0
|
||||
HXA_NT_GEOMETRY = 1
|
||||
HXA_NT_IMAGE = 2
|
||||
|
||||
|
||||
class HXAImageType(IntEnum):
|
||||
HXA_IT_CUBE_IMAGE = 0
|
||||
HXA_IT_1D_IMAGE = 1
|
||||
HXA_IT_2D_IMAGE = 2
|
||||
HXA_IT_3D_IMAGE = 3
|
||||
|
||||
|
||||
class HXAMetaDataType(IntEnum):
|
||||
HXA_MDT_INT64 = 0
|
||||
HXA_MDT_DOUBLE = 1
|
||||
HXA_MDT_NODE = 2
|
||||
HXA_MDT_TEXT = 3
|
||||
HXA_MDT_BINARY = 4
|
||||
HXA_MDT_META = 5
|
||||
|
||||
|
||||
class HXALayerDataType(IntEnum):
|
||||
HXA_LDT_UINT8 = 0
|
||||
HXA_LDT_INT32 = 1
|
||||
HXA_LDT_FLOAT = 2
|
||||
HXA_LDT_DOUBLE = 3
|
||||
|
||||
|
||||
# - Hard conventions
|
||||
|
||||
HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_NAME = "vertex"
|
||||
HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_ID = 0
|
||||
HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_COMPONENTS = 3
|
||||
HXA_CONVENTION_HARD_BASE_CORNER_LAYER_NAME = "reference"
|
||||
HXA_CONVENTION_HARD_BASE_CORNER_LAYER_ID = 0
|
||||
HXA_CONVENTION_HARD_BASE_CORNER_LAYER_COMPONENTS = 1
|
||||
HXA_CONVENTION_HARD_BASE_CORNER_LAYER_TYPE = HXALayerDataType["HXA_LDT_INT32"].value
|
||||
HXA_CONVENTION_HARD_EDGE_NEIGHBOUR_LAYER_NAME = "neighbour"
|
||||
HXA_CONVENTION_HARD_EDGE_NEIGHBOUR_LAYER_TYPE = HXALayerDataType["HXA_LDT_INT32"].value
|
||||
|
||||
|
||||
# - Soft conventions
|
||||
# geometry layers
|
||||
HXA_CONVENTION_SOFT_LAYER_SEQUENCE0 = "sequence"
|
||||
HXA_CONVENTION_SOFT_LAYER_UV0 = "uv"
|
||||
HXA_CONVENTION_SOFT_LAYER_NORMALS = "normal"
|
||||
HXA_CONVENTION_SOFT_LAYER_BINORMAL = "binormal"
|
||||
HXA_CONVENTION_SOFT_LAYER_TANGENT = "tangent"
|
||||
HXA_CONVENTION_SOFT_LAYER_COLOR = "color"
|
||||
HXA_CONVENTION_SOFT_LAYER_CREASES = "creases"
|
||||
HXA_CONVENTION_SOFT_LAYER_SELECTION = "select"
|
||||
HXA_CONVENTION_SOFT_LAYER_SKIN_WEIGHT = "skining_weight"
|
||||
HXA_CONVENTION_SOFT_LAYER_SKIN_REFERENCE = "skining_reference"
|
||||
HXA_CONVENTION_SOFT_LAYER_BLENDSHAPE = "blendshape"
|
||||
HXA_CONVENTION_SOFT_LAYER_ADD_BLENDSHAPE = "addblendshape"
|
||||
HXA_CONVENTION_SOFT_LAYER_MATERIAL_ID = "material"
|
||||
HXA_CONVENTION_SOFT_LAYER_GROUP_ID = "group"
|
||||
|
||||
# Image layers
|
||||
HXA_CONVENTION_SOFT_ALBEDO = "albedo"
|
||||
HXA_CONVENTION_SOFT_LIGHT = "light"
|
||||
HXA_CONVENTION_SOFT_DISPLACEMENT = "displacement"
|
||||
HXA_CONVENTION_SOFT_DISTORTION = "distortion"
|
||||
HXA_CONVENTION_SOFT_AMBIENT_OCCLUSION = "ambient_occlusion"
|
||||
|
||||
# tags layers
|
||||
HXA_CONVENTION_SOFT_NAME = "name"
|
||||
HXA_CONVENTION_SOFT_TRANSFORM = "transform"
|
292
io_scene_hxa/hxapy_read_write.py
Normal file
292
io_scene_hxa/hxapy_read_write.py
Normal file
@ -0,0 +1,292 @@
|
||||
from . import hxapy_header as hxa
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# *** Logging functions (start)
|
||||
|
||||
|
||||
def log_meta(meta):
|
||||
log.debug("Meta:")
|
||||
log.debug(f" - name: {meta['name']}")
|
||||
log.debug(f" - type: {hxa.HXAMetaDataType(meta['type']).name}")
|
||||
log.debug(f" - array_length: {len(meta['data'])}")
|
||||
log.debug(f" - data: {meta['data']}")
|
||||
|
||||
|
||||
def log_layer(layer):
|
||||
log.debug(f" - name: {layer['name']}")
|
||||
log.debug(f" - components: {layer['components']}")
|
||||
log.debug(f" - data_type: {hxa.HXALayerDataType(layer['type']).name}")
|
||||
log.debug(f" - data: {layer['data']}\n")
|
||||
|
||||
|
||||
# *** Logging functions (end)
|
||||
|
||||
|
||||
# *** Read functions (start)
|
||||
|
||||
import struct
|
||||
import array
|
||||
|
||||
|
||||
def read_u8(f):
|
||||
return f.read(1)[0]
|
||||
|
||||
|
||||
def read_u32(f):
|
||||
return struct.unpack("<I", f.read(4))[0]
|
||||
|
||||
|
||||
def read_name(f):
|
||||
l = read_u8(f)
|
||||
return f.read(l).decode()
|
||||
|
||||
|
||||
def read_meta(f):
|
||||
meta = {}
|
||||
meta["name"] = read_name(f)
|
||||
mtype = hxa.HXAMetaDataType(read_u8(f))
|
||||
length = read_u32(f)
|
||||
|
||||
if mtype == hxa.HXAMetaDataType.HXA_MDT_INT64:
|
||||
data = read_array1(f, "Q", length)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_DOUBLE:
|
||||
data = read_array1(f, "d", length)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_NODE:
|
||||
data = read_array1(f, "I", length)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_TEXT:
|
||||
data = f.read(length).decode()
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_BINARY:
|
||||
data = f.read(length)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_META:
|
||||
data = [read_meta(f) for _ in range(length)]
|
||||
|
||||
meta["type"] = mtype
|
||||
meta["data"] = data
|
||||
return meta
|
||||
|
||||
|
||||
def read_array1(f, typecode, length):
|
||||
arr = read_array(f, typecode, length)
|
||||
return arr if len(arr) != 1 else arr[0]
|
||||
|
||||
|
||||
def read_array(f, typecode, length):
|
||||
arr = array.array(typecode)
|
||||
arr.fromfile(f, length)
|
||||
return arr
|
||||
|
||||
|
||||
def read_layer(f, count):
|
||||
layer = {}
|
||||
layer["name"] = read_name(f)
|
||||
layer["components"] = read_u8(f)
|
||||
dtype = hxa.HXALayerDataType(read_u8(f))
|
||||
layer["type"] = dtype
|
||||
|
||||
length = count * layer["components"]
|
||||
|
||||
if dtype == hxa.HXALayerDataType.HXA_LDT_UINT8:
|
||||
data = read_array(f, "B", length)
|
||||
elif dtype == hxa.HXALayerDataType.HXA_LDT_INT32:
|
||||
data = read_array(f, "i", length)
|
||||
elif dtype == hxa.HXALayerDataType.HXA_LDT_FLOAT:
|
||||
data = read_array(f, "f", length)
|
||||
elif dtype == hxa.HXALayerDataType.HXA_LDT_DOUBLE:
|
||||
data = read_array(f, "d", length)
|
||||
|
||||
layer["data"] = data
|
||||
log_layer(layer)
|
||||
return layer
|
||||
|
||||
|
||||
def read_layerstack(f, count):
|
||||
layerstack = {}
|
||||
|
||||
layerstack["layer_count"] = read_u32(f)
|
||||
layerstack["layers"] = [
|
||||
read_layer(f, count) for _ in range(layerstack["layer_count"])
|
||||
]
|
||||
|
||||
return layerstack
|
||||
|
||||
|
||||
def read_node(f):
|
||||
node = {}
|
||||
node_type = hxa.HXANodeType(read_u8(f))
|
||||
node["type"] = node_type
|
||||
node["meta_data_count"] = read_u32(f)
|
||||
node["meta_data"] = [read_meta(f) for i in range(node["meta_data_count"])]
|
||||
|
||||
content = {}
|
||||
if node["type"] == hxa.HXANodeType.HXA_NT_GEOMETRY:
|
||||
content["vertex_count"] = read_u32(f)
|
||||
content["vertex_stack"] = read_layerstack(f, content["vertex_count"])
|
||||
|
||||
content["edge_corner_count"] = read_u32(f)
|
||||
content["corner_stack"] = read_layerstack(f, content["edge_corner_count"])
|
||||
content["edge_stack"] = read_layerstack(f, content["edge_corner_count"])
|
||||
|
||||
content["face_count"] = read_u32(f)
|
||||
content["face_stack"] = read_layerstack(f, content["face_count"])
|
||||
|
||||
elif node_type == hxa.HXANodeType.HXA_NT_IMAGE:
|
||||
log.debug("! Not processing images yet\n")
|
||||
|
||||
node["content"] = content
|
||||
return node
|
||||
|
||||
|
||||
def read_hxa(f):
|
||||
magic = f.read(4)
|
||||
if magic != b"HxA\0":
|
||||
raise RuntimeError(
|
||||
"HXA Error: file {f.name} not a HxA file(incorrect magic number"
|
||||
)
|
||||
|
||||
hxa_file = {}
|
||||
hxa_file["version"] = read_u8(f)
|
||||
hxa_file["node_count"] = read_u32(f)
|
||||
|
||||
hxa_file["nodes"] = [read_node(f) for i in range(hxa_file["node_count"])]
|
||||
|
||||
return hxa_file
|
||||
|
||||
|
||||
# *** Read functions (end)
|
||||
|
||||
|
||||
# *** Write functions (start)
|
||||
|
||||
|
||||
def write_str(f, s):
|
||||
f.write(s.encode())
|
||||
|
||||
|
||||
def write_u8(f, v):
|
||||
f.write(struct.pack("<B", v))
|
||||
|
||||
|
||||
def write_u32(f, v):
|
||||
f.write(struct.pack("<I", v))
|
||||
|
||||
|
||||
def ensure_array(v):
|
||||
"""If v isn't already an array, make it a one-element list"""
|
||||
try:
|
||||
_ = len(v)
|
||||
has_len = True
|
||||
except Exception:
|
||||
has_len = False
|
||||
|
||||
is_array = not isinstance(v, dict) and has_len
|
||||
return v if is_array else [v]
|
||||
|
||||
|
||||
def write_array(f, typecode, arr):
|
||||
if isinstance(arr, array.array) and arr.typecode == typecode:
|
||||
arr.tofile(f)
|
||||
else:
|
||||
fmt = f"<{len(arr)}{typecode}"
|
||||
f.write(struct.pack(fmt, *arr))
|
||||
|
||||
|
||||
def write_meta(f, meta):
|
||||
mtype = meta["type"]
|
||||
data = ensure_array(meta["data"])
|
||||
|
||||
write_name(f, meta["name"])
|
||||
write_u8(f, mtype)
|
||||
write_u32(f, len(meta["data"]))
|
||||
|
||||
log_meta(meta)
|
||||
|
||||
if mtype == hxa.HXAMetaDataType.HXA_MDT_INT64:
|
||||
write_array(f, "Q", data)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_DOUBLE:
|
||||
write_array(f, "d", data)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_NODE:
|
||||
write_array(f, "I", data)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_TEXT:
|
||||
write_str(f, data)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_BINARY:
|
||||
write_array(f, "B", data)
|
||||
elif mtype == hxa.HXAMetaDataType.HXA_MDT_META:
|
||||
for child_meta in data:
|
||||
write_meta(f, child_meta)
|
||||
else:
|
||||
assert False # might put a HxA Runtime Error message here
|
||||
|
||||
|
||||
def write_name(f, name):
|
||||
assert len(name) < hxa.HXA_NAME_MAX_LENGTH
|
||||
write_u8(f, len(name))
|
||||
write_str(f, name)
|
||||
|
||||
|
||||
def write_layer(f, layer):
|
||||
dtype = layer["type"]
|
||||
data = layer["data"]
|
||||
|
||||
l = len(layer["name"])
|
||||
write_name(f, layer["name"])
|
||||
write_u8(f, layer["components"])
|
||||
write_u8(f, dtype)
|
||||
|
||||
if dtype == hxa.HXALayerDataType.HXA_LDT_UINT8:
|
||||
write_array(f, "B", data)
|
||||
elif dtype == hxa.HXALayerDataType.HXA_LDT_INT32:
|
||||
write_array(f, "i", data)
|
||||
elif dtype == hxa.HXALayerDataType.HXA_LDT_FLOAT:
|
||||
write_array(f, "f", data)
|
||||
elif dtype == hxa.HXALayerDataType.HXA_LDT_DOUBLE:
|
||||
write_array(f, "d", data)
|
||||
else:
|
||||
assert False # might put a HxA Runtime Error message here
|
||||
|
||||
|
||||
def write_layerstack(f, stack):
|
||||
write_u32(f, len(stack["layers"]))
|
||||
|
||||
for layer in stack["layers"]:
|
||||
write_layer(f, layer)
|
||||
|
||||
|
||||
def write_node(f, node):
|
||||
write_u8(f, node["type"])
|
||||
write_u32(f, node["meta_data_count"])
|
||||
|
||||
for meta in node["meta_data"]:
|
||||
write_meta(f, meta)
|
||||
|
||||
content = node["content"]
|
||||
if node["type"] == hxa.HXANodeType.HXA_NT_GEOMETRY:
|
||||
write_u32(f, content["vertex_count"])
|
||||
write_layerstack(f, content["vertex_stack"])
|
||||
write_u32(f, content["edge_corner_count"])
|
||||
write_layerstack(f, content["corner_stack"])
|
||||
write_layerstack(f, content["edge_stack"])
|
||||
write_u32(f, content["face_count"])
|
||||
write_layerstack(f, content["face_stack"])
|
||||
|
||||
# elif node["type"] == hxa.HXANodeType.IMAGE:
|
||||
# pass
|
||||
|
||||
|
||||
def write_hxa(f, hxa_dict):
|
||||
f.write(b"HxA\0")
|
||||
write_u8(f, hxa_dict["version"])
|
||||
write_u32(f, len(hxa_dict["nodes"]))
|
||||
|
||||
log.debug(f"HxA version: {hxa_dict['version']}")
|
||||
log.debug(f"Node count: {len(hxa_dict['nodes'])}")
|
||||
node_counter = 0
|
||||
for node in hxa_dict["nodes"]:
|
||||
log.debug(f"Node #{node_counter}, {node['type']}")
|
||||
node_counter += 1
|
||||
write_node(f, node)
|
||||
|
||||
|
||||
# *** Write functions (end)
|
48
io_scene_hxa/hxapy_util.py
Normal file
48
io_scene_hxa/hxapy_util.py
Normal file
@ -0,0 +1,48 @@
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def timestamp():
|
||||
return datetime.now().strftime("%d%m%y-%H%M%S")
|
||||
|
||||
|
||||
def time_elapsed(start, end):
|
||||
delta = end - start
|
||||
s = delta.seconds
|
||||
|
||||
h = s // 3600
|
||||
s = s - (h * 3600)
|
||||
m = s // 60
|
||||
s = s - (m * 60)
|
||||
|
||||
if h > 0:
|
||||
return f"Done in: {h}h{m}m{s}s"
|
||||
elif m > 0:
|
||||
return f"Done in: {m}m{s}s"
|
||||
elif s > 0:
|
||||
return f"Done in: {s}s"
|
||||
|
||||
|
||||
def flatten_list(_list):
|
||||
res = []
|
||||
for inner in _list:
|
||||
for el in inner:
|
||||
res.append(el)
|
||||
return res
|
||||
|
||||
|
||||
def restore_faces(references):
|
||||
faces = []
|
||||
tupl = []
|
||||
for r in references:
|
||||
if r < 0:
|
||||
r = -r - 1
|
||||
tupl.append(r)
|
||||
faces.append(tuple(tupl))
|
||||
tupl = []
|
||||
else:
|
||||
tupl.append(r)
|
||||
return faces
|
||||
|
||||
|
||||
def break_list_up(data, length, step):
|
||||
return [tuple(data[x : x + step]) for x in list(range(0, length, step))]
|
144
io_scene_hxa/hxapy_validate.py
Normal file
144
io_scene_hxa/hxapy_validate.py
Normal file
@ -0,0 +1,144 @@
|
||||
from . import hxapy_header as hxa
|
||||
from . import hxapy_read_write as hxa_rw
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def hxa_util_validate_meta(meta, node, count):
|
||||
if meta["type"] == hxa.HXAMetaDataType.HXA_MDT_NODE:
|
||||
for al in range(len(meta["data"])):
|
||||
if meta["data"][al] >= count:
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {node} has meta data {meta['name']} that is referencing a non \
|
||||
existent node ({meta['data'][al]} out of {count})\n"
|
||||
)
|
||||
return False
|
||||
if meta["type"] == hxa.HXAMetaDataType.HXA_MDT_META:
|
||||
for al in range(len(meta["data"])):
|
||||
hxa_util_validate_meta(
|
||||
meta["data"][al],
|
||||
node,
|
||||
count,
|
||||
)
|
||||
|
||||
|
||||
def hxa_util_validate(hxa_file):
|
||||
for nc in range(hxa_file["node_count"]):
|
||||
node = hxa_file["nodes"][nc]
|
||||
for mc in range(node["meta_data_count"]):
|
||||
hxa_util_validate_meta(
|
||||
node["meta_data"][mc],
|
||||
mc,
|
||||
hxa_file["node_count"],
|
||||
)
|
||||
|
||||
node_type = hxa.HXANodeType(node["type"]).value
|
||||
if node_type == hxa.HXANodeType.HXA_NT_GEOMETRY:
|
||||
if node["content"]["vertex_stack"]["layer_count"] == 0:
|
||||
log.info(f"HxA Verify Error: Node {nc} has no vertex layer\n")
|
||||
return False
|
||||
components = node["content"]["vertex_stack"]["layers"][0]["components"]
|
||||
if components != 3:
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {mc} vertex layer vertex layer has {components} components. \
|
||||
Must be HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_COMPONENTS \
|
||||
{hxa.HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_COMPONENTS}.\n"
|
||||
)
|
||||
return False
|
||||
|
||||
layer_type = hxa.HXALayerDataType(
|
||||
node["content"]["vertex_stack"]["layers"][0]["type"]
|
||||
).value
|
||||
if (layer_type != hxa.HXALayerDataType.HXA_LDT_FLOAT) and (
|
||||
layer_type != hxa.HXALayerDataType.HXA_LDT_DOUBLE
|
||||
):
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {nc} first vertex layer is {hxa.HXALayerDataType(layer_type).name}, \
|
||||
must be HXA_LDT_FLOAT or HXA_LDT_DOUBLE\n"
|
||||
)
|
||||
return False
|
||||
|
||||
name = node["content"]["vertex_stack"]["layers"][0]["name"]
|
||||
if name != hxa.HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_NAME:
|
||||
log.info(
|
||||
f'HxA Verify Error: Node {nc} vertex layer is named {name}. \
|
||||
Must be HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_NAME " \
|
||||
{hxa.HXA_CONVENTION_HARD_BASE_VERTEX_LAYER_NAME}".\n'
|
||||
)
|
||||
return False
|
||||
|
||||
if node["content"]["corner_stack"]["layer_count"] != 0:
|
||||
components = node["content"]["corner_stack"]["layers"][0]["components"]
|
||||
if components != 1:
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {nc} reference layer has {components} components. Must be 1.\n"
|
||||
)
|
||||
return False
|
||||
|
||||
layer_type = hxa.HXALayerDataType(
|
||||
node["content"]["corner_stack"]["layers"][0]["type"]
|
||||
).value
|
||||
if layer_type != hxa.HXALayerDataType.HXA_LDT_INT32:
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {nc} reference layer is of type {hxa.HXALayerDataType(layer_type).value} \
|
||||
must be HXA_LDT_INT32\n"
|
||||
)
|
||||
return False
|
||||
|
||||
name = node["content"]["corner_stack"]["layers"][0]["name"]
|
||||
if name != hxa.HXA_CONVENTION_HARD_BASE_CORNER_LAYER_NAME:
|
||||
log.info(
|
||||
f'HxA Verify Error: Node {nc} reference layer is named {name}. Must be \
|
||||
HXA_CONVENTION_HARD_BASE_CORNER_LAYER_NAME " \
|
||||
{hxa.HXA_CONVENTION_HARD_BASE_CORNER_LAYER_NAME}".\n'
|
||||
)
|
||||
return False
|
||||
|
||||
references = node["content"]["corner_stack"]["layers"][0]["data"]
|
||||
poly_count = 0
|
||||
reference = 0
|
||||
# Q: what if edge_corner_count is 0??
|
||||
for cc in range(node["content"]["edge_corner_count"]):
|
||||
if references[cc] < 0:
|
||||
reference = -references[cc] - 1
|
||||
poly_count += 1
|
||||
else:
|
||||
reference = references[cc]
|
||||
|
||||
if reference >= node["content"]["vertex_count"]:
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {nc} has a reference value referencing a non existing \
|
||||
vertex ({reference}).\n"
|
||||
)
|
||||
return False
|
||||
|
||||
face_count = node["content"]["face_count"]
|
||||
if face_count != poly_count:
|
||||
log.info(
|
||||
f"HxA Verify Error: Node {nc} claims to have {face_count} faces but the reference data \
|
||||
has {poly_count} faces.\n"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
argc = len(argv)
|
||||
if argc == 1:
|
||||
log.info("Add a filename (ex: py hxapy_validate.py cube.hxa)")
|
||||
exit()
|
||||
|
||||
# for now, we have a single filename after the script:
|
||||
# - py hxapy_validate.py filename
|
||||
filename = argv[-1]
|
||||
with open(filename, "wb") as f:
|
||||
hxafile = hxa_rw.read_hxa(f)
|
||||
|
||||
valid = hxa_util_validate(hxafile)
|
||||
if not valid:
|
||||
log.info(f"{filename} could not pass validation")
|
||||
if valid:
|
||||
log.info(f"{filename} validated")
|
305
io_scene_hxa/import_hxa_py.py
Normal file
305
io_scene_hxa/import_hxa_py.py
Normal file
@ -0,0 +1,305 @@
|
||||
import bpy
|
||||
from bpy.props import StringProperty
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
||||
from . import hxapy_read_write as hxa_rw
|
||||
from . import hxapy_util as hxa_util
|
||||
from . import hxapy_validate as hxa_valid
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImportHXA(bpy.types.Operator, ImportHelper):
|
||||
"""Import a HxA file as a mesh"""
|
||||
|
||||
bl_idname = "import_model.hxa"
|
||||
bl_label = "Import HxA"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
filename_ext = ".hxa"
|
||||
filter_glob: StringProperty(default="*.hxa", options={"HIDDEN"})
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
f = open(self.filepath, "rb")
|
||||
except OSError:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
f"HXA Error: File {self.filepath} could not be open for reading\n",
|
||||
)
|
||||
log.info(f"HXA Error: File {self.filepath} could not be open for reading\n")
|
||||
return {"CANCELLED"}
|
||||
|
||||
hxa_dict = hxa_rw.read_hxa(f)
|
||||
f.close()
|
||||
|
||||
if not hxa_valid.hxa_util_validate(hxa_dict):
|
||||
self.report({"ERROR"}, f"{self.filepath} couldn't pass validation")
|
||||
log.info(f"{self.filepath} couldn't pass validation")
|
||||
return {"CANCELLED"}
|
||||
|
||||
meta_shapekeys = None
|
||||
meta_armaturedata = None
|
||||
meta_weightindexes = None
|
||||
meta_vertexweights = None
|
||||
meta_customproperties = None
|
||||
meta_creases = None
|
||||
meta_objectname = None
|
||||
meta_meshname = None
|
||||
meta_location = None
|
||||
meta_scale = None
|
||||
meta_armature_location = None
|
||||
meta_armature_scale = None
|
||||
meta_bones_heads = None
|
||||
meta_bones_tails = None
|
||||
meta_bones_names = None
|
||||
meta_bones_parents = None
|
||||
meta_data_count = hxa_dict["nodes"][0]["meta_data_count"]
|
||||
if meta_data_count > 0:
|
||||
meta_data = hxa_dict["nodes"][0]["meta_data"]
|
||||
|
||||
metas_present = {meta["name"]: meta for meta in meta_data}
|
||||
meta_meshdata = metas_present["meta mesh data"]
|
||||
|
||||
if "meta shapekeys" in metas_present.keys():
|
||||
meta_shapekeys = metas_present["meta shapekeys"]
|
||||
|
||||
if "meta armature data" in metas_present.keys():
|
||||
meta_armaturedata = metas_present["meta armature data"]
|
||||
|
||||
if "meta weight indexes" in metas_present.keys():
|
||||
meta_weightindexes = metas_present["meta weight indexes"]
|
||||
|
||||
if "meta vertex weights" in metas_present.keys():
|
||||
meta_vertexweights = metas_present["meta vertex weights"]
|
||||
|
||||
if "meta custom properties" in metas_present.keys():
|
||||
meta_customproperties = metas_present["meta custom properties"]
|
||||
|
||||
if "meta creases" in metas_present.keys():
|
||||
meta_creases = metas_present["meta creases"]
|
||||
|
||||
# ** mesh data
|
||||
meta_meshdata_entries = meta_meshdata["data"]
|
||||
metas_present = {meta["name"]: meta for meta in meta_meshdata_entries}
|
||||
|
||||
if "meta objectname" in metas_present.keys():
|
||||
meta_objectname = metas_present["meta objectname"]
|
||||
log.debug(meta_objectname["data"])
|
||||
|
||||
if "meta meshname" in metas_present.keys():
|
||||
meta_meshname = metas_present["meta meshname"]
|
||||
log.debug(meta_meshname["data"])
|
||||
|
||||
if "meta location" in metas_present.keys():
|
||||
meta_location = metas_present["meta location"]
|
||||
log.debug(meta_location["data"])
|
||||
|
||||
if "meta scale" in metas_present.keys():
|
||||
meta_scale = metas_present["meta scale"]
|
||||
log.debug(meta_scale["data"])
|
||||
|
||||
# ** armature data
|
||||
if meta_armaturedata:
|
||||
meta_armaturedata_entries = meta_armaturedata["data"]
|
||||
metas_present = {
|
||||
meta["name"]: meta for meta in meta_armaturedata_entries
|
||||
}
|
||||
|
||||
if "meta armature location" in metas_present.keys():
|
||||
meta_armature_location = metas_present["meta armature location"]
|
||||
|
||||
if "meta armature scale" in metas_present.keys():
|
||||
meta_armature_scale = metas_present["meta armature scale"]
|
||||
|
||||
if "meta bones heads" in metas_present.keys():
|
||||
meta_bones_heads = metas_present["meta bones heads"]
|
||||
|
||||
if "meta bones tails" in metas_present.keys():
|
||||
meta_bones_tails = metas_present["meta bones tails"]
|
||||
|
||||
if "meta bones names" in metas_present.keys():
|
||||
meta_bones_names = metas_present["meta bones names"]
|
||||
|
||||
if "meta bones parents" in metas_present.keys():
|
||||
meta_bones_parents = metas_present["meta bones parents"]
|
||||
|
||||
vertex_count = hxa_dict["nodes"][0]["content"]["vertex_count"]
|
||||
vert_data = hxa_dict["nodes"][0]["content"]["vertex_stack"]["layers"][0]["data"]
|
||||
ref_data = hxa_dict["nodes"][0]["content"]["corner_stack"]["layers"][0]["data"]
|
||||
|
||||
# - Add edge verts to the mesh, then write creases to mesh after picking out the edges?
|
||||
verts = hxa_util.break_list_up(vert_data, vertex_count * 3, 3)
|
||||
|
||||
if meta_creases:
|
||||
edge_data = meta_creases["data"][0]["data"]
|
||||
arrlen = len(meta_creases["data"][0]["data"])
|
||||
edges = hxa_util.break_list_up(edge_data, arrlen, 2)
|
||||
crease_values = meta_creases["data"][1]["data"]
|
||||
|
||||
crease_dict = {}
|
||||
for i in range(len(edges)):
|
||||
e = edges[i]
|
||||
k = str(f"{e[0]} {e[1]}")
|
||||
v = crease_values[i]
|
||||
crease_dict[k] = v
|
||||
else:
|
||||
edges = [] # for now
|
||||
|
||||
faces = hxa_util.restore_faces(ref_data)
|
||||
|
||||
if meta_meshname:
|
||||
me_name = meta_meshname["data"]
|
||||
else:
|
||||
me_name = "imported HxA mesh"
|
||||
|
||||
if meta_objectname:
|
||||
ob_name = meta_objectname["data"]
|
||||
else:
|
||||
ob_name = "imported HxA object"
|
||||
|
||||
restore_mesh(verts, edges, faces, me_name, ob_name)
|
||||
|
||||
mesh_object = bpy.context.object
|
||||
|
||||
if meta_location:
|
||||
x, y, z = meta_location["data"]
|
||||
|
||||
mesh_object.location.x = x
|
||||
mesh_object.location.y = y
|
||||
mesh_object.location.z = z
|
||||
|
||||
if meta_scale:
|
||||
x, y, z = meta_scale["data"]
|
||||
|
||||
mesh_object.scale.x = x
|
||||
mesh_object.scale.y = y
|
||||
mesh_object.scale.z = z
|
||||
|
||||
if meta_armature_location:
|
||||
armature_location = meta_armature_location["data"]
|
||||
|
||||
if meta_armature_scale:
|
||||
armature_scale = meta_armature_scale["data"]
|
||||
|
||||
if meta_creases:
|
||||
mesh_edges = mesh_object.data.edges
|
||||
edge_verts = [list(e.vertices) for e in mesh_object.data.edges]
|
||||
for i in range(len(crease_values)):
|
||||
e = edge_verts[i]
|
||||
k = str(f"{e[0]} {e[1]}")
|
||||
mesh_edges[i].crease = crease_dict[k]
|
||||
|
||||
if meta_shapekeys:
|
||||
shapekeys_data = meta_shapekeys["data"]
|
||||
|
||||
for i in range(len(shapekeys_data)):
|
||||
shapekeys_values = hxa_util.break_list_up(
|
||||
shapekeys_data[i]["data"], vertex_count * 3, 3
|
||||
)
|
||||
shapekey = mesh_object.shape_key_add(
|
||||
name=shapekeys_data[i]["name"], from_mix=True
|
||||
)
|
||||
for i in range(vertex_count):
|
||||
shapekey.data[i].co = shapekeys_values[i]
|
||||
|
||||
if meta_armaturedata:
|
||||
bone_count = len(meta_bones_heads["data"]) / 3
|
||||
heads = hxa_util.break_list_up(
|
||||
meta_bones_heads["data"], int(bone_count) * 3, 3
|
||||
)
|
||||
|
||||
tails = hxa_util.break_list_up(
|
||||
meta_bones_tails["data"], int(bone_count) * 3, 3
|
||||
)
|
||||
names = [x["data"] for x in meta_bones_names["data"]]
|
||||
parents = [x["data"] for x in meta_bones_parents["data"]]
|
||||
|
||||
restore_armature(
|
||||
armature_location, armature_scale, heads, tails, names, parents
|
||||
)
|
||||
|
||||
# parent armature, apply location and scale
|
||||
ob_arm = bpy.context.object
|
||||
# arm = ob_arm.data
|
||||
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
mesh_object.select_set(True)
|
||||
ob_arm.select_set(True)
|
||||
bpy.context.view_layer.objects.active = ob_arm
|
||||
bpy.ops.object.parent_set(type="ARMATURE_NAME")
|
||||
|
||||
# - does this exist without armatures? (does this need to get indented into the armature block :) )
|
||||
# *** Vertex weights
|
||||
if (meta_weightindexes != None) & (meta_vertexweights != None):
|
||||
vindex_list = meta_weightindexes["data"]
|
||||
vgroup_list = meta_vertexweights["data"]
|
||||
|
||||
# ** write weights
|
||||
for i in range(int(bone_count)):
|
||||
indexes = vindex_list[i]["data"]
|
||||
weights = vgroup_list[i]["data"]
|
||||
vgroup_size = len(weights)
|
||||
for j in range(vgroup_size):
|
||||
mesh_object.vertex_groups[i].add(
|
||||
[indexes[j]], weights[j], "REPLACE"
|
||||
)
|
||||
|
||||
# *** Custom properties
|
||||
# assumption: custom props are saved on the mesh object. It's fine, but something to think about.
|
||||
if meta_customproperties:
|
||||
customprop_entries = meta_customproperties["data"]
|
||||
for customprop in customprop_entries:
|
||||
mesh_object[customprop["name"]] = customprop["data"]
|
||||
|
||||
bpy.ops.object.shade_flat()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def restore_mesh(verts, edges, faces, mesh_name="mesh", object_name="object"):
|
||||
test_mesh = bpy.data.meshes.new(name=mesh_name)
|
||||
test_mesh.from_pydata(verts, edges, faces)
|
||||
|
||||
test_object = bpy.data.objects.new(name=object_name, object_data=test_mesh)
|
||||
|
||||
bpy.context.view_layer.active_layer_collection.collection.objects.link(test_object)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
test_object.select_set(True)
|
||||
bpy.context.view_layer.objects.active = test_object
|
||||
|
||||
|
||||
def restore_armature(location, scale, heads, tails, names, parents):
|
||||
bpy.ops.object.armature_add(enter_editmode=True)
|
||||
ob_arm = bpy.context.object
|
||||
arm = ob_arm.data
|
||||
ob_arm.location = location
|
||||
ob_arm.scale = scale
|
||||
|
||||
arm.edit_bones[-1].head = heads[0]
|
||||
arm.edit_bones[-1].tail = tails[0]
|
||||
arm.edit_bones[-1].name = names[0]
|
||||
|
||||
for i in range(1, len(heads)):
|
||||
ebone = arm.edit_bones.new(names[i])
|
||||
ebone.head = heads[i]
|
||||
ebone.tail = tails[i]
|
||||
|
||||
for i in range(len(parents)):
|
||||
bpy.ops.armature.select_all(action="DESELECT")
|
||||
child_name = names[i]
|
||||
parent_name = parents[i]
|
||||
if parent_name == "":
|
||||
continue
|
||||
|
||||
child = arm.edit_bones[child_name]
|
||||
parent = arm.edit_bones[parent_name]
|
||||
child.parent = parent
|
||||
|
||||
ob_arm.show_in_front = True
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
ob_arm.select_set(True)
|
Reference in New Issue
Block a user