|
8 | 8 | import mathutils |
9 | 9 | import pathlib |
10 | 10 | import re |
| 11 | +import contextlib |
11 | 12 |
|
12 | 13 |
|
13 | 14 | from . import bcf |
14 | 15 | from . import bmf |
15 | 16 | from . import cfp |
16 | 17 | from . import cmx |
| 18 | +from . import xbm |
17 | 19 | from . import skn |
18 | 20 | from . import texture_loader |
19 | 21 | from . import utils |
@@ -531,7 +533,113 @@ def import_skill( # noqa: C901 PLR0912 PLR0915 |
531 | 533 | armature_object.animation_data.action = original_action |
532 | 534 |
|
533 | 535 |
|
534 | | -def import_files( # noqa: C901 PLR0912 PLR0913 |
| 536 | +def import_xbox_model( # noqa: C901 PLR0912 PLR0915 |
| 537 | + context: bpy.types.Context, |
| 538 | + logger: logging.Logger, |
| 539 | + file_path: pathlib.Path, |
| 540 | + xbox_texture_file_list: list[pathlib.Path], |
| 541 | +) -> None: |
| 542 | + """Import an xbox model file.""" |
| 543 | + try: |
| 544 | + model_desc = xbm.read_file(file_path) |
| 545 | + except utils.FileReadError as _: |
| 546 | + logger.info(f"Could not load xbox mesh {file_path}") # noqa: G004 |
| 547 | + return |
| 548 | + |
| 549 | + file_collection = bpy.data.collections.get(model_desc.name) |
| 550 | + if file_collection is None: |
| 551 | + file_collection = bpy.data.collections.new(model_desc.name) |
| 552 | + |
| 553 | + if file_collection.name not in context.collection.children: |
| 554 | + context.collection.children.link(file_collection) |
| 555 | + |
| 556 | + for object_index, object_desc in enumerate(model_desc.objects): |
| 557 | + object_collection_name = f"{model_desc.name} {object_index}" |
| 558 | + |
| 559 | + object_collection = bpy.data.collections.get(object_collection_name) |
| 560 | + if object_collection is None: |
| 561 | + object_collection = bpy.data.collections.new(object_collection_name) |
| 562 | + |
| 563 | + if object_collection.name not in file_collection.children: |
| 564 | + file_collection.children.link(object_collection) |
| 565 | + |
| 566 | + for mesh_index, mesh_desc in enumerate(object_desc.meshes): |
| 567 | + mesh_name = f"{model_desc.name} {object_index} {mesh_index}" |
| 568 | + |
| 569 | + mesh = bpy.data.meshes.new(mesh_name) |
| 570 | + obj = bpy.data.objects.new(mesh_name, mesh) |
| 571 | + |
| 572 | + object_collection.objects.link(obj) |
| 573 | + |
| 574 | + b_mesh = bmesh.new() |
| 575 | + |
| 576 | + for vertex in mesh_desc.positions: |
| 577 | + position = mathutils.Vector(vertex.position) |
| 578 | + b_mesh.verts.new(position) |
| 579 | + |
| 580 | + b_mesh.verts.ensure_lookup_table() |
| 581 | + b_mesh.verts.index_update() |
| 582 | + |
| 583 | + if len(mesh_desc.faces): |
| 584 | + for i in range(len(mesh_desc.faces) - 2): |
| 585 | + with contextlib.suppress(ValueError): |
| 586 | + b_mesh.faces.new( |
| 587 | + ( |
| 588 | + b_mesh.verts[mesh_desc.faces[i + 2]], |
| 589 | + b_mesh.verts[mesh_desc.faces[i + 1]], |
| 590 | + b_mesh.verts[mesh_desc.faces[i + 0]], |
| 591 | + ), |
| 592 | + ) |
| 593 | + |
| 594 | + deform_layer = b_mesh.verts.layers.deform.verify() |
| 595 | + |
| 596 | + for index, strip in enumerate(mesh_desc.strips): |
| 597 | + vertex_group = obj.vertex_groups.new(name=str(index)) |
| 598 | + |
| 599 | + for i in range(strip[0], strip[1] - 2): |
| 600 | + vert_a = b_mesh.verts[i + 0] |
| 601 | + vert_b = b_mesh.verts[i + 1] |
| 602 | + vert_c = b_mesh.verts[i + 2] |
| 603 | + |
| 604 | + vert_a[deform_layer][vertex_group.index] = 1.0 |
| 605 | + vert_b[deform_layer][vertex_group.index] = 1.0 |
| 606 | + vert_c[deform_layer][vertex_group.index] = 1.0 |
| 607 | + |
| 608 | + if vert_a.co != vert_b.co and vert_a.co != vert_c.co and vert_b.co != vert_c.co: # noqa: PLR1714 |
| 609 | + b_mesh.faces.new((vert_a, vert_b, vert_c)) |
| 610 | + |
| 611 | + if len(mesh_desc.uvs): |
| 612 | + uv_layer = b_mesh.loops.layers.uv.verify() |
| 613 | + for face in b_mesh.faces: |
| 614 | + for loop in face.loops: |
| 615 | + loop[uv_layer].uv = mesh_desc.uvs[loop.vert.index] |
| 616 | + |
| 617 | + b_mesh.to_mesh(mesh) |
| 618 | + b_mesh.free() |
| 619 | + |
| 620 | + if len(mesh_desc.normals) > 0: |
| 621 | + mesh.normals_split_custom_set_from_vertices(mesh_desc.normals) |
| 622 | + |
| 623 | + for polygon in mesh.polygons: |
| 624 | + if polygon.normal.dot(mathutils.Vector(mesh_desc.normals[polygon.vertices[0]])) < 0.0: |
| 625 | + polygon.flip() |
| 626 | + |
| 627 | + mesh.normals_split_custom_set_from_vertices(mesh_desc.normals) |
| 628 | + |
| 629 | + mesh.update() |
| 630 | + |
| 631 | + texture_id_string = f'{mesh_desc.texture_id:x}' |
| 632 | + for file_path in xbox_texture_file_list: |
| 633 | + if file_path.stem.endswith(texture_id_string): |
| 634 | + texture_loader.create_material(obj, file_path.stem, file_path) |
| 635 | + |
| 636 | + if not obj.data.materials: |
| 637 | + for file_path in xbox_texture_file_list: |
| 638 | + if file_path.stem.lower().startswith(f"{model_desc.name.lower()} "): |
| 639 | + texture_loader.create_material(obj, file_path.stem, file_path) |
| 640 | + |
| 641 | + |
| 642 | +def import_files( # noqa: C901 PLR0912 PLR0913 PLR0915 |
535 | 643 | context: bpy.types.Context, |
536 | 644 | logger: logging.Logger, |
537 | 645 | file_paths: list[pathlib.Path], |
@@ -640,3 +748,15 @@ def import_files( # noqa: C901 PLR0912 PLR0913 |
640 | 748 | for bcf_file_path, bcf_file in bcf_files: |
641 | 749 | for skill in bcf_file.skills: |
642 | 750 | import_skill(context, logger, bcf_file_path.parent, animation_file_list, skill) |
| 751 | + |
| 752 | + file_search_directory_xbox = pathlib.Path( |
| 753 | + context.preferences.addons["io_scene_ts1"].preferences.file_search_directory_xbox, |
| 754 | + ) |
| 755 | + if file_search_directory_xbox == "": |
| 756 | + file_search_directory_xbox = file_paths[0].parent |
| 757 | + file_list_xbox = list(file_search_directory_xbox.glob("*.png")) |
| 758 | + |
| 759 | + for file_path in file_paths: |
| 760 | + match file_path.suffix: |
| 761 | + case ".xbm": |
| 762 | + import_xbox_model(context, logger, file_path, file_list_xbox) |
0 commit comments