Привет. вот код аддона для блендер 3.3.0, потом скажу что делать:

bl_info = {
    "name": "F1 Track AI Progress Maker ",
    "author": "AlonsoBeast",
    "version": (1, 0, 2),
    "blender": (3, 3, 0),
    "location": "View3D > Sidebar > F1 Track Builder Toolkit",
    "description": "Tool for managing and transforming track progress curves",
    "category": "3D View",
}

import bpy
import xml.etree.ElementTree as ET
from bpy.props import StringProperty, IntProperty, BoolProperty
from bpy.types import Operator, Panel
from bpy.props import FloatProperty
from bpy_extras.io_utils import ImportHelper, ExportHelper
import tempfile
import shutil
from mathutils import Vector

def create_plane(name, size=2.0):
    # Создаем меш с плоскостью
    mesh = bpy.data.meshes.new(name)
    obj = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(obj)

    # Создание вершин и граней
    verts = [
        (-size / 2, -size / 2, 0),
        (size / 2, -size / 2, 0),
        (size / 2, size / 2, 0),
        (-size / 2, size / 2, 0),
    ]
    
    faces = [(0, 1, 2, 3)]  # Плоская грань (4 вершины)
    
    mesh.from_pydata(verts, [], faces)
    mesh.update()

    return obj

class CREATE_OT_add_new_shape_track(Operator):
    bl_idname = "create.add_new_shape_track"
    bl_label = "Add New Shape Track"
    bl_description = "Creates a new track target object."

    def execute(self, context):
        # Создаем новый объект Plane
        mesh = bpy.data.meshes.new(name="TRACK_TARGET")
        obj = bpy.data.objects.new("TRACK_TARGET", mesh)
        context.collection.objects.link(obj)

        # Создаем плоскость вручную
        verts = [
            (-1.0, -1.0, 0.0),
            (1.0, -1.0, 0.0),
            (1.0, 1.0, 0.0),
            (-1.0, 1.0, 0.0),
        ]
        
        faces = [(0, 1, 2, 3)]  # Одна грань (плоскость)

        mesh.from_pydata(verts, [], faces)
        mesh.update()

        # Устанавливаем позицию плоскости
        obj.location = (0, 0, 0)

        return {'FINISHED'}


def create_spline(name, points):
    curve_data = bpy.data.curves.new(name=name, type='CURVE')
    curve_data.dimensions = '3D'

    spline = curve_data.splines.new('POLY')
    spline.points.add(len(points) - 1)

    for i, (x, y, z) in enumerate(points):
        spline.points[i].co = (x, y, z, 1)

    curve_object = bpy.data.objects.new(name, curve_data)
    bpy.context.collection.objects.link(curve_object)

    return curve_object

def mirror_y_global(obj):
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)
    bpy.ops.object.mode_set(mode='OBJECT') #... .... ....  ..  .......  .... ...
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    bpy.ops.transform.mirror(orient_type='GLOBAL', constraint_axis=(False, True, False))
    obj.select_set(False)

def create_mesh_from_splines(left_points, right_points):
    if len(left_points) != len(right_points):
        raise ValueError("Left and right point lists have different lengths")

    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))  # Точка слева
        vertices.append((right_coord.x, right_coord.z, right_coord.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("PROGRESS_MESH")
    mesh_data.from_pydata(vertices, edges, faces)
    mesh_data.update()

    # Создание нового объекта
    mesh_object = bpy.data.objects.new("PROGRESS_MODEL", mesh_data)
    bpy.context.collection.objects.link(mesh_object)

    # Применение глобального зеркалирования к мешу
    mirror_y_global(mesh_object)

    return mesh_object


class IMPORT_OT_ai_progress_track_maker(bpy.types.Operator, ImportHelper):
    bl_idname = "import_mesh.ai_progress_track_maker"
    bl_label = "Import AI Track XML"
    filename_ext = ".xml"
    filter_glob: StringProperty(default="*.xml", options={'HIDDEN'})

    def execute(self, context):
        try:
            # Создание временного файла
            temp_filepath = tempfile.mktemp(suffix=".xml")
            shutil.copy(self.filepath, temp_filepath)
            context.scene.temp_ai_progress_file = temp_filepath

            # Парсинг XML
            tree = ET.parse(temp_filepath)
            root = tree.getroot()

            # Обработка gates
            gates_element = root.find('gates')
            if gates_element is not None:
                left_points = []
                right_points = []
                for gate in gates_element.findall('gate'):
                    left = gate.find('left')
                    right = gate.find('right')

                    if left is None or right is None:
                        continue

                    # Извлекаем координаты в порядке X, Y, Z
                    left_coords = [float(x) for x in left.text.split()]
                    right_coords = [float(x) for x in right.text.split()]

                    left_points.append(Vector((left_coords[0], left_coords[1], left_coords[2])))
                    right_points.append(Vector((right_coords[0], right_coords[1], right_coords[2])))

                # Создаем комбинированный меш из точек
                create_mesh_from_splines(left_points, right_points)

            else:
                self.report({'WARNING'}, "<gates> element not found in the file.")

            # Обработка points для создания 2DMAP_SPLINE
            points_element = root.find('points')
            if points_element is not None:
                point_coords = []
                for line in points_element.findall('line'):
                    if line.get("type") == "progress":
                        for point in line.findall('point'):
                            position = point.find('position')
                            if position is not None:
                                coords = [float(x) for x in position.text.split()]
                                point_coords.append((coords[0], coords[2], coords[1]))  # X, Z, Y
                twodmap_spline = create_spline("2DMAP_SPLINE", point_coords)
                mirror_y_global(twodmap_spline)  # Зеркальное отражение для 2DMAP_SPLINE
            else:
                self.report({'WARNING'}, "<points> element not found in the file.")
        except ET.ParseError:
            self.report({'ERROR'}, "Error reading XML file.")
            return {'CANCELLED'}

        return {'FINISHED'}

class EXPORT_OT_ai_progress_track_maker(Operator, ExportHelper):
    bl_idname = "export_mesh.ai_progress_track_maker"
    bl_label = "Export AI Track XML"
    filename_ext = ".xml"
    filter_glob: StringProperty(default="*.xml", options={'HIDDEN'})

    def execute(self, context):
        temp_filepath = context.scene.get("temp_ai_progress_file", "")
        if not temp_filepath:
            self.report({'ERROR'}, "Temporary file not found. Please re-import the file.")
            return {'CANCELLED'}

        try:
            self.update_xml_with_current_coordinates(temp_filepath)
            shutil.copy(temp_filepath, self.filepath)

            # Успешный экспорт
            self.report({'INFO'}, "Progress track was exported successfully.")
        except Exception as e:
            self.report({'ERROR'}, f"Failed to export: {str(e)}")
            return {'CANCELLED'}

        return {'FINISHED'}

    def update_xml_with_current_coordinates(self, temp_filepath):
        try:
            tree = ET.parse(temp_filepath)
            root = tree.getroot()
        except ET.ParseError:
            self.report({'ERROR'}, "Error reading temporary XML file.")
            return {'CANCELLED'}

        gates_element = root.find('gates')
        if gates_element is None:
            self.report({'ERROR'}, "Element <gates> not found in the file.")
            return {'CANCELLED'}

        # Находим PROGRESS_MODEL
        combined_mesh_object = bpy.context.scene.objects.get("PROGRESS_MODEL")

        if not combined_mesh_object:
            self.report({'ERROR'}, "PROGRESS_MODEL not found in the scene.")
            return {'CANCELLED'}

        # Получаем координаты из PROGRESS_MODEL и делаем конвертацию XZY в XYZ
        vertices = combined_mesh_object.data.vertices
        left_points = []
        right_points = []

        for i in range(0, len(vertices), 2):
            if i + 1 < len(vertices):  # Проверка на наличие правой точки
                left_coord = vertices[i].co
                right_coord = vertices[i + 1].co

                # Конвертация из XZY в XYZ
                left_points.append((left_coord.x, -left_coord.z, left_coord.y))  # X, -Z, Y
                right_points.append((right_coord.x, -right_coord.z, right_coord.y))  # X, -Z, Y

        # Обновляем данные для gates
        for i, gate in enumerate(gates_element.findall('gate')):
            left_element = gate.find('left')
            right_element = gate.find('right')

            if left_element is not None and right_element is not None:
                if i < len(left_points) and i < len(right_points):
                    left_coords = left_points[i]
                    right_coords = right_points[i]

                    # Записываем в формате X, Y, Z
                    left_text = f"{round(left_coords[0], 3)} {round(-left_coords[1], 3)} {round(left_coords[2], 3)}"  # XYZ
                    right_text = f"{round(right_coords[0], 3)} {round(-right_coords[1], 3)} {round(right_coords[2], 3)}"  # XYZ

                    left_element.text = left_text
                    right_element.text = right_text

        # Обновление элемента <points> для 2DMAP_SPLINE
        points_element = root.find('points')
        if points_element is None:
            points_element = ET.SubElement(root, 'points')

        # Обновляем точки 2DMAP_SPLINE с глобальным зеркалированием Y
        twodmap_spline = bpy.context.scene.objects.get("2DMAP_SPLINE")
        twodmap_points = twodmap_spline.data.splines[0].points

        for i, point in enumerate(twodmap_points):
            x_coord = point.co.x
            y_coord = point.co.y
            z_coord = point.co.z

            # Применяем глобальное зеркалирование Y
            # Обратите внимание: Y координата станет отрицательной
            y_coord_mirrored = -y_coord

            # Учитываем переход с XYZ (Blender) на XZY для экспорта
            if i < len(points_element.findall('line')[0].findall('point')):
                point_element = points_element.find('line').findall('point')[i]
            else:
                point_element = ET.SubElement(points_element.find('line'), 'point', id=str(i))

            position_element = point_element.find('position')
            if position_element is not None:
                # Конвертация: X, -Y становится X, Z, Y для XZY
                position_element.text = f"{round(x_coord, 3)} {round(z_coord, 3)} {round(y_coord, 3)}"  # X, Z, -Y
            else:
                position_element = ET.SubElement(point_element, 'position', format="float3")
                position_element.text = f"{round(x_coord, 3)} {round(z_coord, 3)} {round(y_coord, 3)}"  # X, Z, -Y

        # Запись обновленного дерева обратно в файл
        tree.write(temp_filepath, encoding='utf-8', xml_declaration=True)

class CREATE_OT_add_new_shape_track(Operator):
    bl_idname = "create.add_new_shape_track"
    bl_label = "Add New Shape Track"
    bl_description = "Creates a new track target object."

    def execute(self, context):
        # Создаем новый объект Plane
        mesh = bpy.data.meshes.new(name="TRACK_TARGET")
        obj = bpy.data.objects.new("TRACK_TARGET", mesh)
        context.collection.objects.link(obj)

        # Создание вершин и граней
        verts = [
            (-1.0, -1.0, 0.0),
            (1.0, -1.0, 0.0),
            (1.0, 1.0, 0.0),
            (-1.0, 1.0, 0.0),
        ]
        
        faces = [(0, 1, 2, 3)]  # Одна грань (плоскость)

        # Заполнение меша
        mesh.from_pydata(verts, [], faces)
        mesh.update()

        # Устанавливаем позицию плоскости
        obj.location = (0, 0, 0)

        return {'FINISHED'}
        
class TRANSFER_OT_progress_mesh_shape_transfer(Operator):
    bl_idname = "transfer.progress_mesh_shape_transfer"
    bl_label = "Transfer Progress Mesh Shape"
    bl_description = "Transfer vertex positions from the new mesh to PROGRESS_MODEL while preserving the adjusted widths of gates."

    def execute(self, context):
        # Получаем объект PROGRESS_MODEL
        combined_mesh_object = context.scene.objects.get("PROGRESS_MODEL")
        if not combined_mesh_object or combined_mesh_object.type != 'MESH':
            self.report({'ERROR'}, "PROGRESS_MODEL not found or is not a MESH.")
            return {'CANCELLED'}

        # Получаем целевой объект TRACK_TARGET
        target_mesh = context.scene.objects.get("TRACK_TARGET")
        if not target_mesh or target_mesh.type != 'MESH':
            self.report({'ERROR'}, "TRACK_TARGET not found or is not a MESH.")
            return {'CANCELLED'}

        # Получаем вершины TRACK_TARGET
        target_vertices = target_mesh.data.vertices
        num_target_vertices = len(target_vertices)

        # Проверка четности количества вершин
        if num_target_vertices % 2 != 0:
            self.report({'ERROR'}, "The number of vertices in TRACK_TARGET must be an even number (paired gates).")
            return {'CANCELLED'}

        # Число гейтов TRACK_TARGET
        target_gate_count = num_target_vertices // 2

        # --- Сохраняем текущую ширину гейтов из TRACK_TARGET ---
        target_gate_widths = []
        for i in range(target_gate_count):
            # Координаты текущих левого и правого вертексов
            left_vertex = target_vertices[i * 2].co
            right_vertex = target_vertices[i * 2 + 1].co
            # Вычисление ширины гейта (расстояние между левым и правым вертексами)
            gate_width = (right_vertex - left_vertex).length
            target_gate_widths.append(gate_width)

        # --- Получаем вершины PROGRESS_MODEL ---
        combined_vertices = combined_mesh_object.data.vertices
        num_combined_vertices = len(combined_vertices)

        # Проверяем четность количества вершин
        if num_combined_vertices % 2 != 0:
            self.report({'ERROR'}, "The number of vertices in PROGRESS_MODEL must also be an even number.")
            return {'CANCELLED'}

        # Число гейтов в PROGRESS_MESH
        combined_gate_count = num_combined_vertices // 2

        # --- Собираем гейты TRACK_TARGET как пары вершин ---
        target_gates = [
            (
                target_vertices[i * 2].co,       # Координаты левой вершины
                target_vertices[i * 2 + 1].co   # Координаты правой вершины
            )
            for i in range(target_gate_count)
        ]

        # ---  Перенос данных в PROGRESS_MESH ---
        for i in range(combined_gate_count):
            # Нормализуем индекс по количеству гейтов PROGRESS_MESH
            normalized_t = i / (combined_gate_count - 1)
            

            # Определяем индекс текущего и следующего гейта в TRACK_TARGET
            target_index = int(normalized_t * (target_gate_count - 1))
            local_t = (normalized_t * (target_gate_count - 1)) % 1.0

            # Берем текущий и следующий гейты
            current_gate = target_gates[target_index]
            next_gate = target_gates[min(target_index + 1, target_gate_count - 1)]

            # Направление движения между гейтами (вектор направления)
            direction_vector = (next_gate[0] + next_gate[1]) * 0.5 - (current_gate[0] + current_gate[1]) * 0.5

            # Проверка, чтобы избежать сингулярности (нулевого вектора направления)
            if direction_vector.length == 0:
                direction_vector = Vector((0.0, 1.0, 0.0))  # Задаем безопасное направление вверх

            direction_vector.normalize()

            # Вектор перпендикуляра для разделения левых и правых вершин
            perpendicular_vector = direction_vector.cross(Vector((0.0, 0.0, 1.0)))
            perpendicular_vector.normalize()

            # Центр гейта: интерполяция среднего положения текущего и следующего гейтов
            interpolated_center = (current_gate[0].lerp(next_gate[0], local_t) + current_gate[1].lerp(next_gate[1], local_t)) * 0.5

            # Используем ширину из TRACK_TARGET для этого сегмента
            gate_width = target_gate_widths[target_index] if target_index < len(target_gate_widths) else 1.0

            # Применяем ширину для корректировки левых и правых вершин...... .. ..
            interpolated_left = interpolated_center - perpendicular_vector * (gate_width / 2)
            interpolated_right = interpolated_center + perpendicular_vector * (gate_width / 2)

            # Записываем новые координаты в PROGRESS_MESH
            combined_vertices[i * 2].co = (
                interpolated_right.x,
                interpolated_right.y, 
                interpolated_right.z
            )
            combined_vertices[i * 2 + 1].co = (
                interpolated_left.x,
                interpolated_left.y,  
                interpolated_left.z
            )
            
        # Печатаем текущие координаты всех вершин в combined_vertices для диагностики
        print("Current Vertex Coordinates in PROGRESS_MODEL:")
        for idx, vert in enumerate(combined_vertices):
            print(f"Vertex {idx}: {vert.co}")

        # Далее ваша существующая логика
        num_combined_vertices = len(combined_vertices)

        if num_combined_vertices < 4 or num_combined_vertices % 2 != 0:
            self.report({'ERROR'}, "The number of vertices in PROGRESS_MODEL must be at least 4 and an even number.")
            return {'CANCELLED'}

        # --- Обработка последних двух вершин ---
        if num_combined_vertices >= 4:  # Убедимся, что есть достаточно вершин
            last_vertex_index = num_combined_vertices - 1  # Индекс последнего вектора (например, 413)
            penultimate_vertex_index = num_combined_vertices - 2  # Индекс предпоследнего вектора (например, 412)

            # Получаем индексы для двух предыдущих вершин (409 и 410)
            ref_vertex_index_1 = penultimate_vertex_index - 2  # Это будет 409
            ref_vertex_index_2 = penultimate_vertex_index - 1  # Это будет 410

            # Получаем координаты предыдущих вершин
            ref_vertex_1 = combined_vertices[ref_vertex_index_1].co  # 409
            ref_vertex_2 = combined_vertices[ref_vertex_index_2].co  # 410

            # Печатаем отладочные сообщения
            print(f"Ref Vertex 1 (logical index {ref_vertex_index_1}): {ref_vertex_1}")
            print(f"Ref Vertex 2 (logical index {ref_vertex_index_2}): {ref_vertex_2}")

            # Находим нормаль между ними
            direction_vector = (ref_vertex_2 - ref_vertex_1).normalized()
            perpendicular_vector = direction_vector.cross(Vector((0.0, 0.0, 1.0))).normalized()

            # Координты для новых вершин, смещенные на 5 единиц вперед по нормали
            offset_distance = 20.0
            new_left_vertex = ref_vertex_1 + perpendicular_vector * offset_distance  # Это станет 413
            new_right_vertex = ref_vertex_2 + perpendicular_vector * offset_distance  # Это станет 412

            # Печатаем новые координаты для отладки
            print(f"New Left Vertex (Expected 412): {new_right_vertex}, New Right Vertex (Expected 413): {new_left_vertex}")

            # Меняем местами координаты (это ключевое изменение)
            combined_vertices[last_vertex_index].co = new_right_vertex           # Устанавливаем для 413
            combined_vertices[penultimate_vertex_index].co = new_left_vertex  # Устанавливаем для 412

            # Печатаем сообщение об успешном изменении
            print(f"Updated last vertices: "
                  f"Vertex {penultimate_vertex_index}: {combined_vertices[penultimate_vertex_index].co}, "
                  f"Vertex {last_vertex_index}: {combined_vertices[last_vertex_index].co}")

        self.report({'INFO'}, "Shape successfully transferred with adjusted widths from TRACK_TARGET.")
        return {'FINISHED'}
        

class SPLINE_OT_select_split_gate(Operator):
    """Select a specific split gate"""
    bl_idname = "spline.select_split_gate"
    bl_label = "Select Split Gate"
    split_gate_idx: IntProperty()

    def execute(self, context):
        combined_mesh_object = context.scene.objects.get("PROGRESS_MODEL")

        if combined_mesh_object and combined_mesh_object.type == 'MESH':
            # Устанавливаем активный объект
            bpy.context.view_layer.objects.active = combined_mesh_object
            
            # Переключаемся в режим объекта
            bpy.ops.object.mode_set(mode='OBJECT')
            
            # Снимаем выделение со всех вершин
            for vertex in combined_mesh_object.data.vertices:
                vertex.select = False  
                
            # Определяем индексы вершин, относящихся к текущему gate
            left_vertex_index = self.split_gate_idx * 2      # Индекс левой вершины
            right_vertex_index = left_vertex_index + 1        # Индекс правой вершины

            # Проверяем доступность индексов
            vertices = combined_mesh_object.data.vertices
            if left_vertex_index < len(vertices) and right_vertex_index < len(vertices):
                # Выделяем соответствующие вершины
                vertices[left_vertex_index].select = True
                vertices[right_vertex_index].select = True
                
                print(f"Selected Split Gate at indexes {left_vertex_index} and {right_vertex_index}.")
                
                # Переключаемся обратно в режим редактирования
                bpy.ops.object.mode_set(mode='EDIT')

                return {'FINISHED'}
            else:
                self.report({'ERROR'}, "Gate index out of range.")
                return {'CANCELLED'}
        else:
            self.report({'ERROR'}, "PROGRESS_MODEL not found or invalid object type.")
            return {'CANCELLED'}

class SPLINE_OT_select_point(Operator):
    bl_idname = "spline.select_point"
    bl_label = "Select Mesh Vertex"
    point_index: IntProperty()
    spline_name: StringProperty()  # Название объекта Mesh

    def execute(self, context):
        mesh_object = bpy.context.scene.objects.get(self.spline_name)
        if mesh_object:
            bpy.context.view_layer.objects.active = mesh_object
            
            # Переключаемся в режим редактирования
            bpy.ops.object.mode_set(mode='EDIT')

            vertices = mesh_object.data.vertices

            # Снимаем выделение со всех вершин
            for vert in vertices:
                vert.select = False

            # Выделяем указанную вершину
            if 0 <= self.point_index < len(vertices):
                vertices[self.point_index].select = True

            return {'FINISHED'}
        
        return {'CANCELLED'}

class SPLINE_OT_select_gate(Operator):
    bl_idname = "progress.select_gate"
    bl_label = "Select Gate"
    point_index: IntProperty()

    def execute(self, context):
        combined_mesh_object = context.scene.objects.get("PROGRESS_MODEL")

        if combined_mesh_object:
            print(f"Active Object: {combined_mesh_object.name}")
            if combined_mesh_object.type == 'MESH':
                # Устанавливаем активный объект
                bpy.context.view_layer.objects.active = combined_mesh_object
                
                # Переключаемся в режим объекта
                bpy.ops.object.mode_set(mode='OBJECT')

                # Снимаем выделение со всех вершин
                for vertex in combined_mesh_object.data.vertices:
                    vertex.select = False

                # Индексы вершин
                left_vertex_index = self.point_index * 2
                right_vertex_index = left_vertex_index + 1
                print(f"Vertex count: {len(combined_mesh_object.data.vertices)}")
                print(f"Left Vertex Index: {left_vertex_index}, Right Vertex Index: {right_vertex_index}")

                # Проверяем и выделяем соответствующие вершины
                if left_vertex_index < len(combined_mesh_object.data.vertices):
                    combined_mesh_object.data.vertices[left_vertex_index].select = True
                    print(f"Left vertex {left_vertex_index} selected.")
                else:
                    print(f"Left vertex index {left_vertex_index} is out of range.")

                if right_vertex_index < len(combined_mesh_object.data.vertices):
                    combined_mesh_object.data.vertices[right_vertex_index].select = True
                    print(f"Right vertex {right_vertex_index} selected.")
                else:
                    print(f"Right vertex index {right_vertex_index} is out of range.")

                # Переключаемся обратно в режим редактирования
                bpy.ops.object.mode_set(mode='EDIT')

                return {'FINISHED'}

        print("PROGRESS_MODEL not found or is not a MESH type.")
        return {'CANCELLED'}

class SPLINE_OT_new_split_gate(Operator):
    bl_idname = "spline.new_split_gate"
    bl_label = "New Split Gate"
    split_idx: IntProperty()

    def execute(self, context):
        combined_mesh_object = bpy.context.scene.objects.get("PROGRESS_MODEL")

        if not combined_mesh_object:
            self.report({'ERROR'}, "PROGRESS_MODEL not found.")
            return {'CANCELLED'}

        # Переключаемся в режим объекта, чтобы обновить выбор
        bpy.context.view_layer.objects.active = combined_mesh_object
        bpy.ops.object.mode_set(mode='OBJECT')
        
        selected_vertices = [v for v in combined_mesh_object.data.vertices if v.select]

        # Переключаемся обратно в режим редактирования
        bpy.ops.object.mode_set(mode='EDIT')

        if len(selected_vertices) != 2:
            self.report({'ERROR'}, "Select exactly two vertices for the new gate.")
            return {'CANCELLED'}

        left_vertex_index = selected_vertices[0].index
        right_vertex_index = selected_vertices[1].index

        # Проверяем, являются ли выбранные вертексы смежными
        if not self.are_vertices_connected(combined_mesh_object, left_vertex_index, right_vertex_index):
            self.report({'ERROR'}, "Select nearest vertices that belong to the same common edge gate.")
            return {'CANCELLED'}
        
        # Обрабатываем временный файл
        temp_filepath = context.scene.get("temp_ai_progress_file", "")
        if not temp_filepath:
            self.report({'ERROR'}, "Temporary file not found.")
            return {'CANCELLED'}
        
        try:
            tree = ET.parse(temp_filepath)
            root = tree.getroot()

            route_element = root.find('.//routes/route[@id="0"]')
            if route_element:
                split_element = route_element.find(f'.//split[@id="{self.split_idx}"]')
                if split_element is not None:
                    split_element.set("gate", str(left_vertex_index // 2))  # Используем индекс левой вершины
                    tree.write(temp_filepath, encoding='utf-8', xml_declaration=True)
                    self.report({'INFO'}, f"Updated split {self.split_idx} with new gate index {left_vertex_index // 2}")
                else:
                    self.report({'ERROR'}, "Split element not found.")
                    return {'CANCELLED'}
        except ET.ParseError:
            self.report({'ERROR'}, "Error reading temporary XML file.")
            return {'CANCELLED'}

        return {'FINISHED'}

    def are_vertices_connected(self, mesh_object, index_a, index_b):
        """ Проверяет, соединены ли две вершины в одном крае (edge). """
        edges = mesh_object.data.edges
        for edge in edges:
            if index_a in edge.vertices and index_b in edge.vertices:
                return True
        return False

class TransferShapeProgressOperator(Operator):
    """Transfer Shape from Target to Source Curve"""
    bl_idname = "progress.transfer_shape"
    bl_label = "Transfer Shape"

    source_curve_name: StringProperty()
    target_curve_name: StringProperty()

    def execute(self, context):
        source_obj = bpy.data.objects.get(self.source_curve_name)
        target_obj = bpy.data.objects.get(self.target_curve_name)

        if not source_obj or not target_obj:
            self.report({'ERROR'}, f"Source or Target curve not found: {self.source_curve_name} or {self.target_curve_name}")
            return {'CANCELLED'}

        source_spline = source_obj.data.splines[0]
        target_spline = target_obj.data.splines[0]

        if not hasattr(source_spline, "points") or not hasattr(target_spline, "bezier_points"):
            self.report({'ERROR'}, "Incorrect spline types")
            return {'CANCELLED'}

        num_source_points = len(source_spline.points)
        num_target_points = len(target_spline.bezier_points)

        if num_target_points == 0:
            self.report({'ERROR'}, "Target spline has no points.")
            return {'CANCELLED'}

        # Перенос формы на основе контроля таргет кривой
        for i in range(num_source_points):
            # Рассчитываем нормализованный параметр t для исходного сплайна
            t = i / (num_source_points - 1)
            # Получаем интерполированные координаты для целевой кривой
            new_position = self.interpolate_position(target_spline, t)

            # Применяем зеркальное отражение
            source_spline.points[i].co = (new_position[0], -new_position[1], new_position[2], 1.0)

        self.report({'INFO'}, f"Transferred shape from {self.target_curve_name} to {self.source_curve_name}")
        return {'FINISHED'}

    def interpolate_position(self, spline, t):
        points = spline.bezier_points
        num_points = len(points)

        if num_points == 0:
            return Vector((0, 0, 0))  # Возвращаем нулевой вектор если не существует точек

        # Рассчитываем индексы точек для интерполяции
        i1 = int(t * (num_points - 1))
        i2 = min(i1 + 1, num_points - 1)

        p1 = points[i1].co
        p2 = points[i2].co

        alpha = (t * (num_points - 1)) % 1.0

        vec1 = Vector(p1)
        vec2 = Vector(p2)

        return vec1.lerp(vec2, alpha)

class CreateTargetProgress(Operator):
    """Create Target Bezier Curves with Vector Handles"""
    bl_idname = "f1.create_target_progress"
    bl_label = "Create Target Surface"
    bl_description = ("Adds two target splines in the scene. "
                      "Draw the track shape and use transfer shape progress "
                      "on them in the next section.")
    
    def execute(self, context):
        curve_names = ["2DMAP_TARGET"]
        for name in curve_names:
            if name not in bpy.data.objects:
                curve_data = bpy.data.curves.new(name=name, type='CURVE')
                curve_data.dimensions = '3D'
                spline = curve_data.splines.new('BEZIER')
                spline.bezier_points.add(1)

                start_point = spline.bezier_points[0]
                start_point.co = Vector((0, 0, 0))
                start_point.handle_left_type = 'VECTOR'
                start_point.handle_right_type = 'VECTOR'
                
                end_point = spline.bezier_points[1]
                end_point.co = Vector((0, 20, 0))
                end_point.handle_left_type = 'VECTOR'
                end_point.handle_right_type = 'VECTOR'

                curve_object = bpy.data.objects.new(name, curve_data)
                context.collection.objects.link(curve_object)
        
        self.report({'INFO'}, "Created 2DMAP_TARGET curves")
        return {'FINISHED'}

class CONVERT_OT_2dmap_to_mesh(Operator):
    bl_idname = "convert.2dmap_to_mesh"
    bl_label = "Convert 2DMAP TO MESH"
    bl_description = "Convert the 2DMAP_SPLINE curve to a mesh."

    def execute(self, context):
        import bpy
        import mathutils
        import time

        # Сначала снимаем выделение со всех объектов
        for obj in bpy.context.scene.objects:
            obj.select_set(False)

        # Затем выделяем только 2DMAP_SPLINE
        spline_object_name = "2DMAP_SPLINE"
        spline_object = bpy.data.objects.get(spline_object_name)

        if spline_object and spline_object.type == 'CURVE':
            spline_object.select_set(True)  # Выделяем объект
            bpy.context.view_layer.objects.active = spline_object  # Делаем объект активным

        # Установим ширину дороги
        width = context.scene.track_width
        spline_object_name = "2DMAP_SPLINE"

        # Получаем объект по имени
        spline_object = bpy.data.objects.get(spline_object_name)

        # Проверяем, существует ли объект и если да, преобразуем его в меш
        if spline_object and spline_object.type == 'CURVE':
            bpy.context.view_layer.objects.active = spline_object
            spline_object.select_set(True)  # Выделяем объект
            bpy.ops.object.convert(target='MESH')  # Преобразуем в меш
            
            # Получаем активный объект (теперь это преобразованный меш)
            obj = bpy.context.active_object

            if obj and obj.type == 'MESH':
                bpy.ops.object.mode_set(mode='EDIT')
                mesh = obj.data
                original_vertices = [v.co.copy() for v in mesh.vertices]
                start_time = time.time()
                new_vertices = []
                
                for i in range(len(original_vertices)):
                    if time.time() - start_time > 10:
                        print("Скрипт выполняется более 10 секунд. Остановка.")
                        break

                    current_point = original_vertices[i]
                    if i < len(original_vertices) - 1:
                        next_point = original_vertices[i + 1]
                    elif i > 0:
                        next_point = original_vertices[i - 1]
                    else:
                        continue

                    direction_vector = (next_point - current_point).normalized()
                    rotation_quaternion = direction_vector.to_track_quat('Z', 'Y')
                    side_vector = rotation_quaternion @ mathutils.Vector((1, 0, 0)) * (width / 2)

                    right_vert = current_point + side_vector
                    left_vert = current_point - side_vector

                    new_vertices.append(left_vert)
                    new_vertices.append(right_vert)

                bpy.ops.object.mode_set(mode='OBJECT')
                num_new_verts = len(new_vertices)
                
                if num_new_verts >= 4:
                    new_vertices[-2], new_vertices[-1] = new_vertices[-1], new_vertices[-2]

                for vert in new_vertices:
                    mesh.vertices.add(1)
                    mesh.vertices[-1].co = vert

                for i in range(0, num_new_verts, 2):
                    if i + 1 < num_new_verts:
                        mesh.edges.add(1)
                        mesh.edges[-1].vertices = (len(mesh.vertices) - num_new_verts + i, len(mesh.vertices) - num_new_verts + i + 1)
                    if i + 2 < num_new_verts:
                        mesh.edges.add(1)
                        mesh.edges[-1].vertices = (len(mesh.vertices) - num_new_verts + i, len(mesh.vertices) - num_new_verts + i + 2)
                        mesh.edges.add(1)
                        mesh.edges[-1].vertices = (len(mesh.vertices) - num_new_verts + i + 1, len(mesh.vertices) - num_new_verts + i + 3)

                bpy.ops.object.mode_set(mode='EDIT')
                bpy.ops.mesh.separate(type='SELECTED')
                bpy.ops.object.mode_set(mode='OBJECT')
                separated_object = bpy.context.selected_objects[-1]
                separated_object.name = "TRACK_TARGET"

                print("Экструдирование завершено и объект переименован в TRACK_TARGET!")

        # Снимаем выделение с TRACK_TARGET
        track_target = bpy.data.objects.get("TRACK_TARGET")
        if track_target:
            track_target.select_set(False)

        # Выделяем только 2DMAP_SPLINE и конвертируем его обратно в кривую
        if spline_object:
            bpy.context.view_layer.objects.active = spline_object
            spline_object.select_set(True)  # Выделяем объект
            bpy.ops.object.convert(target='CURVE')  # Преобразуем обратно в кривую

        print("Объект '2DMAP_SPLINE' конвертирован обратно в кривую.")
        return {'FINISHED'}

class VIEW3D_PT_ai_progress_track_maker_panel(Panel):
    bl_label = "F1 Track AI Progress Maker"
    bl_idname = "VIEW3D_PT_ai_progress_track_maker"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'F1 Track Builder Toolkit'

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        
        layout.label(text="Progress Track Editor", icon='TEMP')

        layout.operator(IMPORT_OT_ai_progress_track_maker.bl_idname, text="Import Track Progress", icon='IMPORT')
        layout.operator(EXPORT_OT_ai_progress_track_maker.bl_idname, text="Export Track Progress", icon='EXPORT')

        # Раздел для переноса формы сплайна
        layout.label(text="Progress Spline Shape Transfer", icon='MOD_REMESH')


        layout.label(text="Target for 2DMAP_SPLINE")
        row = layout.row(align=True)
        row.prop_search(scene, "twodmap_spline_target", bpy.data, "objects", text="")
        twodmap_transfer_op = row.operator(TransferShapeOperator.bl_idname, text="Transfer", icon='ARROW_LEFTRIGHT')
        twodmap_transfer_op.source_curve_name = "2DMAP_SPLINE"
        twodmap_transfer_op.target_curve_name = scene.twodmap_spline_target
        layout.operator(CreateTargetProgress.bl_idname, text="Add New Target Spline", icon='MOD_LINEART')

        # Селектор для выбора нового меша
        layout.label(text="Select Target Mesh")
        row = layout.row(align=True)
        row.prop_search(scene, "new_shape_target", bpy.data, "objects", text="")
        transfer_operator = row.operator(TRANSFER_OT_progress_mesh_shape_transfer.bl_idname, text="Transfer", icon='ARROW_LEFTRIGHT')
        layout.prop(scene, "track_width", text="Width Track")
        
        # Кнопка Конверт
        layout.operator(CONVERT_OT_2dmap_to_mesh.bl_idname, text="Convert 2DMAP_SPLINE TO MESH", icon='ORIENTATION_NORMAL')

        # Раздел для управления воротами
        layout.label(text="Manage Gates", icon='NLA_PUSHDOWN')
        box = layout.box()
        box.prop(scene, "show_gates", icon="TRIA_DOWN" if scene.show_gates else "TRIA_RIGHT", emboss=False, text="Show Gates")

        if scene.show_gates:
            combined_mesh_object = bpy.context.scene.objects.get("PROGRESS_MODEL")
            if combined_mesh_object:
                vertices = combined_mesh_object.data.vertices
                num_vertices = len(vertices)

                # Генерация кнопок "Gate ID" в ряд по 4 кнопки в строке... ... ..... 
                for i in range(0, num_vertices, 4 * 2):  # шаг 4*2, так как у нас пары вершин
                    row = layout.row(align=True)  # Каждая новая строка

                    for j in range(4):
                        index = i + j * 2  # Каждый гейт соответствует паре вершин
                        if index < num_vertices:
                            gate_op = row.operator("spline.select_gate", text=f"Gate {index // 2}")
                            gate_op.point_index = index // 2  # Индекс для ворот

                    # Прерываем, если дошли до конца
                    if index >= num_vertices - 1:
                        break

        # Новый раздел для сплитов
        box = layout.box()
        box.label(text="Track Splits", icon='SNAP_PEEL_OBJECT')

        temp_filepath = context.scene.get("temp_ai_progress_file", "")
        if temp_filepath:
            try:
                tree = ET.parse(temp_filepath)
                root = tree.getroot()
                
                route_element = root.find('.//routes/route[@id="0"]')
                if route_element:
                    for split_element in route_element.findall('.//split'):
                        split_id = split_element.get("id")
                        split_type = split_element.get("type").capitalize()
                        gate_id = split_element.get("gate")

                        row = box.row()
                        row.label(text=f"{split_id} {split_type} / Gate {gate_id}")

                        select_op = row.operator(SPLINE_OT_select_split_gate.bl_idname, text="Select Gate", icon='RESTRICT_SELECT_OFF')
                        select_op.split_gate_idx = int(gate_id)

                        new_gate_op = row.operator(SPLINE_OT_new_split_gate.bl_idname, text="New Gate", icon='FILE_REFRESH')
                        new_gate_op.split_idx = int(split_id)
            except ET.ParseError:
                box.label(text="Error reading XML file.")



# Регистрация и анрегистрация функций
def register():
    bpy.types.Scene.twodmap_spline_target = StringProperty(name="2DMAP Spline Target", default="2DMAP_TARGET")
    bpy.types.Scene.temp_ai_progress_file = StringProperty(name="Temporary AI Progress File Path")
    bpy.types.Scene.new_shape_target = StringProperty(name="New Shape Target", default="TRACK_TARGET")
    bpy.types.Scene.show_gates = BoolProperty(name="Show Gates", default=False)
    bpy.types.Scene.track_width = FloatProperty(name="Width Track", default=40.10, min=5.0)

    bpy.utils.register_class(IMPORT_OT_ai_progress_track_maker)
    bpy.utils.register_class(EXPORT_OT_ai_progress_track_maker)
    bpy.utils.register_class(SPLINE_OT_select_gate)
    bpy.utils.register_class(SPLINE_OT_select_point)
    bpy.utils.register_class(TransferShapeProgressOperator)  # Изменено имя
    bpy.utils.register_class(CreateTargetProgress)
    bpy.utils.register_class(SPLINE_OT_select_split_gate)
    bpy.utils.register_class(SPLINE_OT_new_split_gate)
    bpy.utils.register_class(CREATE_OT_add_new_shape_track)
    bpy.utils.register_class(TRANSFER_OT_progress_mesh_shape_transfer)
    bpy.utils.register_class(VIEW3D_PT_ai_progress_track_maker_panel)
    bpy.utils.register_class(CONVERT_OT_2dmap_to_mesh)

def unregister():
    del bpy.types.Scene.twodmap_spline_target
    del bpy.types.Scene.temp_ai_progress_file
    del bpy.types.Scene.new_shape_target
    del bpy.types.Scene.show_gates
    del bpy.types.Scene.track_width

    bpy.utils.unregister_class(IMPORT_OT_ai_progress_track_maker)
    bpy.utils.unregister_class(EXPORT_OT_ai_progress_track_maker)
    bpy.utils.unregister_class(SPLINE_OT_select_gate)
    bpy.utils.unregister_class(SPLINE_OT_select_point)
    bpy.utils.unregister_class(TransferShapeProgressOperator)  # Изменено имя
    bpy.utils.unregister_class(CreateTargetProgress)
    bpy.utils.unregister_class(SPLINE_OT_select_split_gate)
    bpy.utils.unregister_class(SPLINE_OT_new_split_gate)
    bpy.utils.unregister_class(CREATE_OT_add_new_shape_track)
    bpy.utils.unregister_class(TRANSFER_OT_progress_mesh_shape_transfer)
    bpy.utils.unregister_class(VIEW3D_PT_ai_progress_track_maker_panel)
    bpy.utils.unregister_class(CONVERT_OT_2dmap_to_mesh)

if __name__ == "__main__":
    register()