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