Привет, вот код аддона для блендер 3.3.0, задача следующая, нужно записать модуль экспортера так, чтобы он работал согласно модулю импортера. У нас такая логика, есть файл допустим 14 клб, мы парсим оттуда данные, формируется меш точек как координаты, мы меняем координаты этих точек и записываем их обртано в файл перезаписью байтов именно в тех местах, откуда мы их парсили. ЗАПРЕЩЕНО ДОБАВЛЯТЬ ИХ ПОВЕРХ, КАК ОТДЕЛЬНЫЕ. Нужна именно перезапись секторов. Допустим, если в адресе 0х4321 были указаны координаты 424.1221, мы их в блендере поменяли там переместили например сделав их 411.882 то соотвественно, ты в этот же адрес возвращаешь уже 411.882, при этом, если в других координаты не изменились, то менять и не нужно ничего. Фиксируй только то, что изменилось. Так как оригинальный файл сложный и имеет в себе не только инфу, которую мы парсим но и другие метаданные. Если файл пр импорте 14 клб, то на выходе должен быть 14 клб. Просто перезапиши измененные сектора. 

bl_info = {
    "name": "F1 Track Reducemechanics",
    "blender": (3, 3, 0),
    "category": "3D View",
}

import struct
import bpy
from mathutils import Vector

class F1ImportTrackOperator(bpy.types.Operator):
    """Import F1 Track"""
    bl_idname = "f1.import_track"
    bl_label = "Import Track"
    bl_options = {'REGISTER', 'UNDO'}
    
    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        import_track(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

class F1ExportTrackOperator(bpy.types.Operator):
    """Export F1 Track"""
    bl_idname = "f1.export_track"
    bl_label = "Export Track"
    bl_options = {'REGISTER', 'UNDO'}
    
    filepath: bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        export_track(self.filepath)
        return {'FINISHED'}

    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

def import_track(filename):
    left_points = []
    right_points = []

    with open(filename, 'rb') as f:
        version = struct.unpack('i', f.read(4))[0]

        while True:
            gate_data = f.read(17)
            if not gate_data:
                break
            
            x = struct.unpack('BBB', gate_data[0:3])
            y = struct.unpack('BBB', gate_data[3:6])
            z = struct.unpack('BBB', gate_data[6:9])
            xo = struct.unpack('b', gate_data[9:10])[0]
            yo = struct.unpack('b', gate_data[10:11])[0]
            zo = struct.unpack('b', gate_data[11:12])[0]
            racingAcrossProportion = struct.unpack('B', gate_data[12:13])[0]
            leftNormalX = struct.unpack('b', gate_data[13:14])[0]
            leftNormalZ = struct.unpack('b', gate_data[14:15])[0]
            rightNormalX = struct.unpack('b', gate_data[15:16])[0]
            rightNormalZ = struct.unpack('b', gate_data[16:17])[0]

            # Decompress data
            bX = ((x[0] << 16) + (x[1] << 8) + x[2] - 0x800000) / 200.0
            bY = ((y[0] << 16) + (y[1] << 8) + y[2] - 0x800000) / 200.0
            bZ = ((z[0] << 16) + (z[1] << 8) + z[2] - 0x800000) / 200.0
            
            oX = xo / 50.0
            oY = yo / 100.0
            oZ = zo / 50.0
            
            lx = bX - oX
            ly = bY - oY
            lz = bZ - oZ
            
            rx = bX + oX
            ry = bY + oY
            rz = bZ + oZ
            
            left_points.append(Vector((lx, ly, lz)))
            right_points.append(Vector((rx, ry, rz)))

    mesh_object = create_mesh_from_splines(left_points, right_points)
    apply_global_y_mirror(mesh_object)

def create_mesh_from_splines(left_points, right_points):
    if len(left_points) != len(right_points):
        raise ValueError("Left and right point lists must be the same length.")

    vertices = []
    edges = []
    faces = []

    for i in range(len(left_points)):
        left_coord = left_points[i]
        right_coord = right_points[i]

        vertices.append((left_coord.x, left_coord.z, left_coord.y))  # X, Z, Y
        vertices.append((right_coord.x, right_coord.z, right_coord.y))  # X, Z, Y

        if i < len(left_points) - 1:
            idx1 = i * 2
            idx2 = idx1 + 1
            idx3 = idx1 + 2
            idx4 = idx3 + 1

            edges.append((idx1, idx2))
            faces.append((idx1, idx2, idx4, idx3))

    mesh_data = bpy.data.meshes.new("RaceTrackMesh")
    mesh_data.from_pydata(vertices, edges, faces)
    mesh_data.update()

    mesh_object = bpy.data.objects.new("RaceTrack", mesh_data)
    bpy.context.collection.objects.link(mesh_object)

    return mesh_object

def apply_global_y_mirror(obj):
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)

    bpy.ops.object.mode_set(mode='OBJECT')
    bpy.ops.transform.mirror(orient_type='GLOBAL', constraint_axis=(False, True, False))
    obj.select_set(False)


class F1TrackBuilderPanel(bpy.types.Panel):
    """Creates a panel for the F1 Track Builder Toolkit"""
    bl_label = "F1 Track Builder Toolkit"
    bl_idname = "PT_F1_Track_Builder"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'F1 Track Builder Toolkit'

    def draw(self, context):
        layout = self.layout
        layout.operator(F1ImportTrackOperator.bl_idname, text="Import Track")
        layout.operator(F1ExportTrackOperator.bl_idname, text="Export Track")

def register():
    bpy.utils.register_class(F1ImportTrackOperator)
    bpy.utils.register_class(F1ExportTrackOperator)
    bpy.utils.register_class(F1TrackBuilderPanel)

def unregister():
    bpy.utils.unregister_class(F1ImportTrackOperator)
    bpy.utils.unregister_class(F1ExportTrackOperator)
    bpy.utils.unregister_class(F1TrackBuilderPanel)

if __name__ == "__main__":
    register()