исходники перенесены в Src. итоговый файл - DebugVarEdit.exe
улучшена таблица (растянута и форматирована) добавлен скрипт для компиляции .exe новые переменные можно добавлять в xml через .c напрямую (записываешь в .c он видит новую переменную и записывает в xml) можно удалять переменные из xml по del в окне выбора всех переменных надо подумать как реализовать выбор массивов и пофиксить баги: - кривая запись пути к файлу переменной в xml
This commit is contained in:
		
							parent
							
								
									4517087194
								
							
						
					
					
						commit
						0b50c31aa8
					
				
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@ -11,7 +11,7 @@ from scanVars import *
 | 
			
		||||
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
 | 
			
		||||
 | 
			
		||||
class VariableSelectorDialog(QDialog):
 | 
			
		||||
    def __init__(self, all_vars, structs, typedefs, parent=None):
 | 
			
		||||
    def __init__(self, all_vars, structs, typedefs, xml_path=None, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.setWindowTitle("Выбор переменных")
 | 
			
		||||
        self.resize(600, 500)
 | 
			
		||||
@ -23,6 +23,8 @@ class VariableSelectorDialog(QDialog):
 | 
			
		||||
        self.expanded_vars = []
 | 
			
		||||
        self.var_map = {v['name']: v for v in all_vars}
 | 
			
		||||
 | 
			
		||||
        self.xml_path = xml_path  # сохраняем путь к xml
 | 
			
		||||
 | 
			
		||||
        self.search_input = QLineEdit()
 | 
			
		||||
        self.search_input.setPlaceholderText("Поиск по имени переменной...")
 | 
			
		||||
        self.search_input.textChanged.connect(self.filter_tree)
 | 
			
		||||
@ -59,6 +61,7 @@ class VariableSelectorDialog(QDialog):
 | 
			
		||||
 | 
			
		||||
        self.populate_tree()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_tree_item_recursively(self, parent, var):
 | 
			
		||||
        """
 | 
			
		||||
        Рекурсивно добавляет переменную и её дочерние поля в дерево.
 | 
			
		||||
@ -166,7 +169,7 @@ class VariableSelectorDialog(QDialog):
 | 
			
		||||
            name = item.text(0)  # имя переменной (в колонке 1)
 | 
			
		||||
            type_str = item.text(1)  # тип переменной (в колонке 2)
 | 
			
		||||
 | 
			
		||||
            if not name or not type_str:
 | 
			
		||||
            if not name:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            self.selected_names.append((name, type_str))
 | 
			
		||||
@ -219,3 +222,58 @@ class VariableSelectorDialog(QDialog):
 | 
			
		||||
    def set_tool(self, item, text):
 | 
			
		||||
        item.setToolTip(0, text)
 | 
			
		||||
        item.setToolTip(1, text)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def keyPressEvent(self, event):
 | 
			
		||||
        if event.key() == Qt.Key_Delete:
 | 
			
		||||
            self.delete_selected_vars()
 | 
			
		||||
        else:
 | 
			
		||||
            super().keyPressEvent(event)
 | 
			
		||||
 | 
			
		||||
    def delete_selected_vars(self):
 | 
			
		||||
        # Деактивируем (удаляем из видимых) выбранные переменные
 | 
			
		||||
        for item in self.tree.selectedItems():
 | 
			
		||||
            name = item.text(0)
 | 
			
		||||
            if not name:
 | 
			
		||||
                continue
 | 
			
		||||
            if name in self.var_map:
 | 
			
		||||
                var = self.var_map[name]
 | 
			
		||||
                var['show_var'] = 'false'
 | 
			
		||||
                var['enable'] = 'false'
 | 
			
		||||
 | 
			
		||||
        if not hasattr(self, 'xml_path') or not self.xml_path:
 | 
			
		||||
            from PySide6.QtWidgets import QMessageBox
 | 
			
		||||
            QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        import xml.etree.ElementTree as ET
 | 
			
		||||
        tree = ET.parse(self.xml_path)
 | 
			
		||||
        root = tree.getroot()
 | 
			
		||||
 | 
			
		||||
        if root is None:
 | 
			
		||||
            return
 | 
			
		||||
        
 | 
			
		||||
        vars_section = root.find('variables')
 | 
			
		||||
        if vars_section is None:
 | 
			
		||||
            return  # Нет секции variables — ничего удалять
 | 
			
		||||
 | 
			
		||||
        selected_names = [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
 | 
			
		||||
 | 
			
		||||
        removed_any = False
 | 
			
		||||
        for var_elem in vars_section.findall('var'):
 | 
			
		||||
            name = var_elem.attrib.get('name')
 | 
			
		||||
            if name in selected_names:
 | 
			
		||||
                vars_section.remove(var_elem)
 | 
			
		||||
                removed_any = True
 | 
			
		||||
                if name in self.var_map:
 | 
			
		||||
                    del self.var_map[name]
 | 
			
		||||
                # Удаляем элементы из списка на месте
 | 
			
		||||
                self.all_vars[:] = [v for v in self.all_vars if v['name'] != name]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if removed_any:
 | 
			
		||||
            ET.indent(tree, space="  ", level=0)
 | 
			
		||||
            tree.write(self.xml_path, encoding='utf-8', xml_declaration=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        self.populate_tree()
 | 
			
		||||
@ -129,6 +129,98 @@ def get_iq_define(vtype):
 | 
			
		||||
    else:
 | 
			
		||||
        return 't_iq_none'
 | 
			
		||||
 | 
			
		||||
def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
 | 
			
		||||
    """
 | 
			
		||||
    new_vars — dict: ключ = имя переменной, значение = словарь с info (type, file, extern, static, enable, show_var и т.п.)
 | 
			
		||||
 | 
			
		||||
    Если переменной нет в XML (в <variables>), добавляем её и сохраняем XML-файл.
 | 
			
		||||
 | 
			
		||||
    Возвращает True если что-то добавлено и XML перезаписан, иначе False.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Считываем существующие переменные
 | 
			
		||||
    parsed_vars = {}
 | 
			
		||||
    if os.path.isfile(output_path):
 | 
			
		||||
        with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
 | 
			
		||||
            for line in f:
 | 
			
		||||
                # {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"},
 | 
			
		||||
                m = re.match(
 | 
			
		||||
    r'{\s*\(char\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"',
 | 
			
		||||
                    line)
 | 
			
		||||
                if m:
 | 
			
		||||
                    full_varname = m.group(1)  # e.g., some.deep.var.name
 | 
			
		||||
                    pt_type = m.group(2)
 | 
			
		||||
                    iq_type = m.group(3)
 | 
			
		||||
                    shortname = m.group(4)
 | 
			
		||||
 | 
			
		||||
                    parsed_vars[full_varname] = {
 | 
			
		||||
                        'pt_type': pt_type,
 | 
			
		||||
                        'iq_type': iq_type,
 | 
			
		||||
                        'enable': True,
 | 
			
		||||
                        'show_var': True,
 | 
			
		||||
                        'shortname': shortname,
 | 
			
		||||
                        'return_type': 'int',
 | 
			
		||||
                        'type': '',     # Можешь дополнить из externs
 | 
			
		||||
                        'file': '',     # Можешь дополнить из externs
 | 
			
		||||
                        'extern': False,
 | 
			
		||||
                        'static': False,
 | 
			
		||||
                        'name': full_varname  # Сохраняем исходное имя переменной
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
    if not parsed_vars:
 | 
			
		||||
        print("[INFO] Не удалось найти ни одной переменной в debug_vars.c")
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    xml_full_path = os.path.join(proj_path, xml_rel_path)
 | 
			
		||||
    xml_full_path = os.path.normpath(xml_full_path)
 | 
			
		||||
 | 
			
		||||
    tree = ET.parse(xml_full_path)
 | 
			
		||||
    root = tree.getroot()
 | 
			
		||||
 | 
			
		||||
    vars_section = root.find("variables")
 | 
			
		||||
    if vars_section is None:
 | 
			
		||||
        vars_section = ET.SubElement(root, "variables")
 | 
			
		||||
 | 
			
		||||
    existing_var_names = {v.attrib['name'] for v in vars_section.findall("var")}
 | 
			
		||||
    added_count = 0
 | 
			
		||||
 | 
			
		||||
    # 3. Добавляем переменные, которых нет в XML
 | 
			
		||||
    for name, info in parsed_vars.items():
 | 
			
		||||
        if name in existing_var_names:
 | 
			
		||||
            # Уже есть — обновляем enable, если нужно
 | 
			
		||||
            existing_elem = vars_section.find(f"./var[@name='{name}']")
 | 
			
		||||
            if existing_elem is not None:
 | 
			
		||||
                manual_elem = existing_elem.find("manual")
 | 
			
		||||
                if manual_elem and manual_elem.text == "true":
 | 
			
		||||
                    show_elem = existing_elem.find("show_var")
 | 
			
		||||
                    if show_elem is None:
 | 
			
		||||
                        show_elem = ET.SubElement(existing_elem, "enable")
 | 
			
		||||
                    enable_elem.text = "true"
 | 
			
		||||
                    enable_elem = existing_elem.find("enable")
 | 
			
		||||
                    if enable_elem is None:
 | 
			
		||||
                        enable_elem = ET.SubElement(existing_elem, "enable")
 | 
			
		||||
                    enable_elem.text = "true"
 | 
			
		||||
                    added_count += 1
 | 
			
		||||
            continue
 | 
			
		||||
        var_elem = ET.SubElement(vars_section, "var", {"name": name})
 | 
			
		||||
        manual = ET.SubElement(var_elem, 'manual')
 | 
			
		||||
        manual.text = 'true'
 | 
			
		||||
        for key, val in info.items():
 | 
			
		||||
            elem = ET.SubElement(var_elem, key)
 | 
			
		||||
            if isinstance(val, bool):
 | 
			
		||||
                elem.text = "true" if val else "false"
 | 
			
		||||
            else:
 | 
			
		||||
                elem.text = str(val)
 | 
			
		||||
        added_count += 1
 | 
			
		||||
 | 
			
		||||
    if added_count > 0:
 | 
			
		||||
        ET.indent(tree, space="  ", level=0)
 | 
			
		||||
        tree.write(xml_full_path, encoding="utf-8", xml_declaration=True)
 | 
			
		||||
        print(f"[INFO] В XML добавлено новых переменных: {added_count}")
 | 
			
		||||
        return True
 | 
			
		||||
    else:
 | 
			
		||||
        print("[INFO] Все переменные уже есть в XML.")
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_vars_from_xml(proj_path, xml_rel_path):
 | 
			
		||||
@ -192,29 +284,20 @@ def read_vars_from_xml(proj_path, xml_rel_path):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def  generate_vars_file(proj_path, xml_path, output_dir):
 | 
			
		||||
def generate_vars_file(proj_path, xml_path, output_dir):
 | 
			
		||||
    output_dir = os.path.join(proj_path, output_dir)
 | 
			
		||||
    os.makedirs(output_dir, exist_ok=True)
 | 
			
		||||
    output_path = os.path.join(output_dir, 'debug_vars.c')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Запись новых переменных для в XML
 | 
			
		||||
    add_new_vars_to_xml(proj_path, xml_path, output_path)
 | 
			
		||||
    # Генерируем новые переменные
 | 
			
		||||
    vars, includes, externs = read_vars_from_xml(proj_path, xml_path)
 | 
			
		||||
 | 
			
		||||
    # Сортируем новые переменные по алфавиту по имени
 | 
			
		||||
    sorted_new_debug_vars = dict(sorted(vars.items()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Считываем существующие переменные
 | 
			
		||||
    existing_debug_vars = {}
 | 
			
		||||
    if os.path.isfile(output_path):
 | 
			
		||||
        with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
 | 
			
		||||
            old_lines = f.readlines()
 | 
			
		||||
        for line in old_lines:
 | 
			
		||||
            m = re.match(r'\s*{.*?,\s+.*?,\s+.*?,\s+"([_a-zA-Z][_a-zA-Z0-9]*)"\s*},', line)
 | 
			
		||||
            if m:
 | 
			
		||||
                varname = m.group(1)
 | 
			
		||||
                existing_debug_vars[varname] = line.strip()
 | 
			
		||||
 | 
			
		||||
    new_debug_vars = {}
 | 
			
		||||
 | 
			
		||||
    def is_true(val):
 | 
			
		||||
@ -237,9 +320,6 @@ def  generate_vars_file(proj_path, xml_path, output_dir):
 | 
			
		||||
 | 
			
		||||
        path = info["file"]
 | 
			
		||||
 | 
			
		||||
        if vname in existing_debug_vars:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        iq_type = info.get('iq_type')
 | 
			
		||||
        if not iq_type:
 | 
			
		||||
            iq_type = get_iq_define(vtype)
 | 
			
		||||
@ -269,7 +349,7 @@ def  generate_vars_file(proj_path, xml_path, output_dir):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    # Объединяем все переменные
 | 
			
		||||
    all_debug_lines = [str(v) for v in existing_debug_vars.values()] + [str(v) for v in new_debug_vars.values()]
 | 
			
		||||
    all_debug_lines = new_debug_vars.values()
 | 
			
		||||
 | 
			
		||||
    out_lines = []
 | 
			
		||||
    out_lines.append("// Этот файл сгенерирован автоматически")
 | 
			
		||||
@ -1,3 +1,6 @@
 | 
			
		||||
# build command
 | 
			
		||||
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp setupVars_GUI.py
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
import subprocess
 | 
			
		||||
@ -14,19 +17,21 @@ from PySide6.QtWidgets import (
 | 
			
		||||
    QApplication, QWidget, QTableWidget, QTableWidgetItem,
 | 
			
		||||
    QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
 | 
			
		||||
    QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
 | 
			
		||||
    QDialog, QTreeWidget, QTreeWidgetItem
 | 
			
		||||
    QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy
 | 
			
		||||
)
 | 
			
		||||
from PySide6.QtGui import QTextCursor, QKeyEvent
 | 
			
		||||
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QTimer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class rows(IntEnum):
 | 
			
		||||
    include = 0
 | 
			
		||||
    name = 1
 | 
			
		||||
    type = 2
 | 
			
		||||
    pt_type = 3
 | 
			
		||||
    iq_type = 4
 | 
			
		||||
    ret_type = 5
 | 
			
		||||
    short_name = 6
 | 
			
		||||
    No = 0
 | 
			
		||||
    include = 1
 | 
			
		||||
    name = 2
 | 
			
		||||
    type = 3
 | 
			
		||||
    pt_type = 4
 | 
			
		||||
    iq_type = 5
 | 
			
		||||
    ret_type = 6
 | 
			
		||||
    short_name = 7
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EmittingStream(QObject):
 | 
			
		||||
@ -95,6 +100,8 @@ class VarEditor(QWidget):
 | 
			
		||||
        self.makefile_path = None
 | 
			
		||||
        self.structs_path = None
 | 
			
		||||
        self.output_path = None
 | 
			
		||||
        self._updating = False  # Флаг блокировки рекурсии
 | 
			
		||||
        self._resizing = False  # флаг блокировки повторного вызова
 | 
			
		||||
        self.initUI()
 | 
			
		||||
 | 
			
		||||
    def initUI(self):
 | 
			
		||||
@ -106,7 +113,7 @@ class VarEditor(QWidget):
 | 
			
		||||
        xml_layout = QHBoxLayout()
 | 
			
		||||
        xml_layout.addWidget(QLabel("XML Output:"))
 | 
			
		||||
        self.xml_output_edit = QLineEdit()
 | 
			
		||||
        self.xml_output_edit.returnPressed.connect(self.read_xml_file)
 | 
			
		||||
        self.xml_output_edit.returnPressed.connect(self.update)
 | 
			
		||||
        self.xml_output_edit.textChanged.connect(self.__on_xml_path_changed)
 | 
			
		||||
        xml_layout.addWidget(self.xml_output_edit)
 | 
			
		||||
        btn_xml_browse = QPushButton("...")
 | 
			
		||||
@ -118,7 +125,7 @@ class VarEditor(QWidget):
 | 
			
		||||
        proj_layout = QHBoxLayout()
 | 
			
		||||
        proj_layout.addWidget(QLabel("Project Path:"))
 | 
			
		||||
        self.proj_path_edit = QLineEdit()
 | 
			
		||||
        self.proj_path_edit.returnPressed.connect(self.read_xml_file)
 | 
			
		||||
        self.proj_path_edit.returnPressed.connect(self.update)
 | 
			
		||||
        self.proj_path_edit.textChanged.connect(self.__on_proj_path_changed)
 | 
			
		||||
        proj_layout.addWidget(self.proj_path_edit)
 | 
			
		||||
        btn_proj_browse = QPushButton("...")
 | 
			
		||||
@ -130,7 +137,7 @@ class VarEditor(QWidget):
 | 
			
		||||
        makefile_layout = QHBoxLayout()
 | 
			
		||||
        makefile_layout.addWidget(QLabel("Makefile Path (relative path):"))
 | 
			
		||||
        self.makefile_edit = QLineEdit()
 | 
			
		||||
        self.makefile_edit.returnPressed.connect(self.read_xml_file)
 | 
			
		||||
        self.makefile_edit.returnPressed.connect(self.update)
 | 
			
		||||
        self.makefile_edit.textChanged.connect(self.__on_makefile_path_changed)
 | 
			
		||||
        makefile_layout.addWidget(self.makefile_edit)
 | 
			
		||||
        btn_makefile_browse = QPushButton("...")
 | 
			
		||||
@ -155,9 +162,10 @@ class VarEditor(QWidget):
 | 
			
		||||
        self.btn_update_vars.clicked.connect(self.update_vars_data)
 | 
			
		||||
 | 
			
		||||
        # Таблица переменных
 | 
			
		||||
        self.table = QTableWidget(len(self.vars_list), 7)
 | 
			
		||||
        self.table = QTableWidget(len(self.vars_list), 8)
 | 
			
		||||
        self.table.setHorizontalHeaderLabels([
 | 
			
		||||
            'Include',
 | 
			
		||||
            '№',             # новый столбец
 | 
			
		||||
            'En',
 | 
			
		||||
            'Name',
 | 
			
		||||
            'Origin Type',
 | 
			
		||||
            'Pointer Type',
 | 
			
		||||
@ -187,9 +195,68 @@ class VarEditor(QWidget):
 | 
			
		||||
        layout.addLayout(source_output_layout)
 | 
			
		||||
        layout.addWidget(btn_save)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        header = self.table.horizontalHeader()
 | 
			
		||||
        # Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
 | 
			
		||||
 | 
			
		||||
        for col in range(self.table.columnCount()):
 | 
			
		||||
            if col == self.table.columnCount() - 1:
 | 
			
		||||
                header.setSectionResizeMode(col, QHeaderView.Stretch)
 | 
			
		||||
            else:
 | 
			
		||||
                header.setSectionResizeMode(col, QHeaderView.Interactive)
 | 
			
		||||
 | 
			
		||||
        parent_widget = self.table.parentWidget()
 | 
			
		||||
        if parent_widget:
 | 
			
		||||
            w = parent_widget.width()
 | 
			
		||||
            h = parent_widget.height()
 | 
			
		||||
        viewport_width = self.table.viewport().width()
 | 
			
		||||
        # Сделаем колонки с номерами фиксированной ширины
 | 
			
		||||
        self.table.setColumnWidth(rows.No, 30)
 | 
			
		||||
        self.table.setColumnWidth(rows.include, 30)
 | 
			
		||||
        self.table.setColumnWidth(rows.pt_type, 85)
 | 
			
		||||
        self.table.setColumnWidth(rows.iq_type, 85)
 | 
			
		||||
        self.table.setColumnWidth(rows.ret_type, 85)
 | 
			
		||||
 | 
			
		||||
        self.table.setColumnWidth(rows.name, 300)
 | 
			
		||||
        self.table.setColumnWidth(rows.type, 100)
 | 
			
		||||
        
 | 
			
		||||
        self.table.horizontalHeader().sectionResized.connect(self.on_section_resized)
 | 
			
		||||
 | 
			
		||||
        self.setLayout(layout)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def on_section_resized(self, logicalIndex, oldSize, newSize):
 | 
			
		||||
        if self._resizing:
 | 
			
		||||
            return  # предотвращаем рекурсию
 | 
			
		||||
 | 
			
		||||
        min_width = 50
 | 
			
		||||
        delta = newSize - oldSize
 | 
			
		||||
        right_index = logicalIndex + 1
 | 
			
		||||
 | 
			
		||||
        if right_index >= self.table.columnCount():
 | 
			
		||||
            # Если правая колока - нет соседа, ограничиваем минимальную ширину
 | 
			
		||||
            if newSize < min_width:
 | 
			
		||||
                self._resizing = True
 | 
			
		||||
                self.table.setColumnWidth(logicalIndex, min_width)
 | 
			
		||||
                self._resizing = False
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self._resizing = True
 | 
			
		||||
        try:
 | 
			
		||||
            right_width = self.table.columnWidth(right_index)
 | 
			
		||||
            new_right_width = right_width - delta
 | 
			
		||||
 | 
			
		||||
            # Если соседняя колонка станет уже минимальной - подкорректируем левую
 | 
			
		||||
            if new_right_width < min_width:
 | 
			
		||||
                new_right_width = min_width
 | 
			
		||||
                newSize = oldSize + (right_width - min_width)
 | 
			
		||||
                self.table.setColumnWidth(logicalIndex, newSize)
 | 
			
		||||
 | 
			
		||||
            self.table.setColumnWidth(right_index, new_right_width)
 | 
			
		||||
        finally:
 | 
			
		||||
            self._resizing = False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    def get_xml_path(self):
 | 
			
		||||
        xml_path = self.xml_output_edit.text().strip()
 | 
			
		||||
@ -288,7 +355,6 @@ class VarEditor(QWidget):
 | 
			
		||||
            }
 | 
			
		||||
            vars_out.append(var_data)
 | 
			
		||||
 | 
			
		||||
        # Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои)
 | 
			
		||||
        self.update_all_paths()
 | 
			
		||||
 | 
			
		||||
        if not self.proj_path or not self.xml_path or not self.output_path:
 | 
			
		||||
@ -298,65 +364,76 @@ class VarEditor(QWidget):
 | 
			
		||||
        try:
 | 
			
		||||
            run_generate(self.proj_path, self.xml_path, self.output_path)
 | 
			
		||||
            QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
 | 
			
		||||
            self.update()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            QMessageBox.critical(self, "Ошибка при генерации", str(e))
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    def read_xml_file(self):
 | 
			
		||||
        self.update_all_paths()
 | 
			
		||||
        if self.xml_path and not os.path.isfile(self.xml_path):
 | 
			
		||||
            return
 | 
			
		||||
    def update(self):
 | 
			
		||||
        if self._updating:
 | 
			
		||||
            return  # Уже в процессе обновления — выходим, чтобы избежать рекурсии
 | 
			
		||||
        self._updating = True
 | 
			
		||||
 | 
			
		||||
        self.update_all_paths()
 | 
			
		||||
        try:
 | 
			
		||||
            root, tree = safe_parse_xml(self.xml_path)
 | 
			
		||||
            if root is None:
 | 
			
		||||
            if self.xml_path and not os.path.isfile(self.xml_path):
 | 
			
		||||
                return
 | 
			
		||||
            
 | 
			
		||||
            if not self.proj_path:
 | 
			
		||||
                # Если в поле ничего нет, пробуем взять из XML
 | 
			
		||||
                proj_path_from_xml = root.attrib.get('proj_path', '').strip()
 | 
			
		||||
                if proj_path_from_xml and os.path.isdir(proj_path_from_xml):
 | 
			
		||||
                    self.proj_path = proj_path_from_xml
 | 
			
		||||
                    self.proj_path_edit.setText(proj_path_from_xml)
 | 
			
		||||
            try:
 | 
			
		||||
                root, tree = safe_parse_xml(self.xml_path)
 | 
			
		||||
                if root is None:
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
                if not self.proj_path:
 | 
			
		||||
                    # Если в поле ничего нет, пробуем взять из XML
 | 
			
		||||
                    proj_path_from_xml = root.attrib.get('proj_path', '').strip()
 | 
			
		||||
                    if proj_path_from_xml and os.path.isdir(proj_path_from_xml):
 | 
			
		||||
                        self.proj_path = proj_path_from_xml
 | 
			
		||||
                        self.proj_path_edit.setText(proj_path_from_xml)
 | 
			
		||||
                    else:
 | 
			
		||||
                        QMessageBox.warning(
 | 
			
		||||
                            self,
 | 
			
		||||
                            "Внимание",
 | 
			
		||||
                            "Путь к проекту (proj_path) не найден или не существует.\n"
 | 
			
		||||
                            "Пожалуйста, укажите его вручную в поле 'Project Path'."
 | 
			
		||||
                        )
 | 
			
		||||
                else:
 | 
			
		||||
                    QMessageBox.warning(
 | 
			
		||||
                        self,
 | 
			
		||||
                        "Внимание",
 | 
			
		||||
                        "Путь к проекту (proj_path) не найден или не существует.\n"
 | 
			
		||||
                        "Пожалуйста, укажите его вручную в поле 'Project Path'."
 | 
			
		||||
                    )
 | 
			
		||||
            else:
 | 
			
		||||
                if not os.path.isdir(self.proj_path):
 | 
			
		||||
                    QMessageBox.warning(
 | 
			
		||||
                        self,
 | 
			
		||||
                        "Внимание",
 | 
			
		||||
                        f"Указанный путь к проекту не существует:\n{self.proj_path}\n"
 | 
			
		||||
                        "Пожалуйста, исправьте путь в поле 'Project Path'."
 | 
			
		||||
                    )
 | 
			
		||||
                    if not os.path.isdir(self.proj_path):
 | 
			
		||||
                        QMessageBox.warning(
 | 
			
		||||
                            self,
 | 
			
		||||
                            "Внимание",
 | 
			
		||||
                            f"Указанный путь к проекту не существует:\n{self.proj_path}\n"
 | 
			
		||||
                            "Пожалуйста, исправьте путь в поле 'Project Path'."
 | 
			
		||||
                        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            # --- makefile_path из атрибута ---
 | 
			
		||||
            makefile_path = root.attrib.get('makefile_path', '').strip()
 | 
			
		||||
            makefile_path_full = make_absolute_path(makefile_path, self.proj_path)
 | 
			
		||||
            if makefile_path_full and os.path.isfile(makefile_path_full):
 | 
			
		||||
                self.makefile_edit.setText(makefile_path)
 | 
			
		||||
                self.makefile_path = makefile_path_full
 | 
			
		||||
                if not self.makefile_path:
 | 
			
		||||
                    # --- makefile_path из атрибута ---
 | 
			
		||||
                    makefile_path = root.attrib.get('makefile_path', '').strip()
 | 
			
		||||
                    makefile_path_full = make_absolute_path(makefile_path, self.proj_path)
 | 
			
		||||
                    if makefile_path_full and os.path.isfile(makefile_path_full):
 | 
			
		||||
                        self.makefile_edit.setText(makefile_path)
 | 
			
		||||
                    else:
 | 
			
		||||
                        self.makefile_path = None
 | 
			
		||||
 | 
			
		||||
            # --- structs_path из атрибута ---
 | 
			
		||||
            structs_path = root.attrib.get('structs_path', '').strip()
 | 
			
		||||
            structs_path_full = make_absolute_path(structs_path, self.proj_path)
 | 
			
		||||
            if structs_path_full and os.path.isfile(structs_path_full):
 | 
			
		||||
                self.structs_path = structs_path_full
 | 
			
		||||
                self.structs, self.typedef_map = parse_structs(structs_path_full)
 | 
			
		||||
            else:
 | 
			
		||||
                self.structs_path = None
 | 
			
		||||
                if not self.structs_path:
 | 
			
		||||
                    # --- structs_path из атрибута ---
 | 
			
		||||
                    structs_path = root.attrib.get('structs_path', '').strip()
 | 
			
		||||
                    structs_path_full = make_absolute_path(structs_path, self.proj_path)
 | 
			
		||||
                    if structs_path_full and os.path.isfile(structs_path_full):
 | 
			
		||||
                        self.structs_path = structs_path_full
 | 
			
		||||
                        self.structs, self.typedef_map = parse_structs(structs_path_full)
 | 
			
		||||
                    else:
 | 
			
		||||
                        self.structs_path = None
 | 
			
		||||
                
 | 
			
		||||
            self.vars_list = parse_vars(self.xml_path, self.typedef_map)
 | 
			
		||||
            self.update_table()
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
 | 
			
		||||
                self.vars_list = parse_vars(self.xml_path, self.typedef_map)
 | 
			
		||||
                self.update_table()
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        finally:
 | 
			
		||||
            self._updating = False  # Снимаем блокировку при выходе из функции
 | 
			
		||||
 | 
			
		||||
    def __browse_proj_path(self):
 | 
			
		||||
        dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
 | 
			
		||||
@ -406,38 +483,31 @@ class VarEditor(QWidget):
 | 
			
		||||
 | 
			
		||||
    def __on_xml_path_changed(self):
 | 
			
		||||
        self.xml_path = self.get_xml_path()
 | 
			
		||||
        self.read_xml_file()
 | 
			
		||||
        self.update()
 | 
			
		||||
 | 
			
		||||
    def __on_proj_path_changed(self):
 | 
			
		||||
        self.proj_path = self.get_proj_path()
 | 
			
		||||
        self.read_xml_file()
 | 
			
		||||
        self.update()
 | 
			
		||||
 | 
			
		||||
    def __on_makefile_path_changed(self):
 | 
			
		||||
        self.makefile_path = self.get_makefile_path()
 | 
			
		||||
        if self.makefile_path and self.proj_path:
 | 
			
		||||
            path = make_relative_path(self.makefile_path, self.proj_path)
 | 
			
		||||
            self.makefile_edit.setText(path)
 | 
			
		||||
            self.makefile_path = path
 | 
			
		||||
        self.read_xml_file()
 | 
			
		||||
        self.update()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __after_scanvars_finished(self):
 | 
			
		||||
        xml_path = self.xml_output_edit.text().strip()
 | 
			
		||||
        if not os.path.isfile(xml_path):
 | 
			
		||||
            QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}")
 | 
			
		||||
        self.update_all_paths()
 | 
			
		||||
        if not os.path.isfile(self.xml_path):
 | 
			
		||||
            QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            # Читаем структуры, если задан путь
 | 
			
		||||
            if self.structs_path and os.path.isfile(self.structs_path):
 | 
			
		||||
                try:
 | 
			
		||||
                    self.structs, self.typedef_map = parse_structs(self.structs_path)
 | 
			
		||||
                    # При необходимости обновите UI или сделайте что-то с self.structs
 | 
			
		||||
                except Exception as e:
 | 
			
		||||
                    QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_path}:\n{e}")
 | 
			
		||||
                
 | 
			
		||||
            self.vars_list = parse_vars(xml_path)
 | 
			
		||||
            self.update_table()
 | 
			
		||||
            self.makefile_path = None
 | 
			
		||||
            self.structs_path = None
 | 
			
		||||
            self.proj_path = None
 | 
			
		||||
            self.update()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
@ -466,11 +536,10 @@ class VarEditor(QWidget):
 | 
			
		||||
            QMessageBox.warning(self, "Нет переменных", "Сначала загрузите или обновите переменные.")
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        dlg = VariableSelectorDialog(self.vars_list, self.structs, self.typedef_map, self)
 | 
			
		||||
        dlg = VariableSelectorDialog(self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
 | 
			
		||||
        if dlg.exec():
 | 
			
		||||
            self.write_to_xml()
 | 
			
		||||
            self.read_xml_file()
 | 
			
		||||
            self.update_table()
 | 
			
		||||
            self.update()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -480,8 +549,14 @@ class VarEditor(QWidget):
 | 
			
		||||
        iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
 | 
			
		||||
        filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
 | 
			
		||||
        self.table.setRowCount(len(filtered_vars))
 | 
			
		||||
        self.table.verticalHeader().setVisible(False)
 | 
			
		||||
 | 
			
		||||
        for row, var in enumerate(filtered_vars):
 | 
			
		||||
            # Добавляем номер строки в колонку No (0)
 | 
			
		||||
            no_item = QTableWidgetItem(str(row))
 | 
			
		||||
            no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)  # readonly
 | 
			
		||||
            self.table.setItem(row, rows.No, no_item)
 | 
			
		||||
 | 
			
		||||
            cb = QCheckBox()
 | 
			
		||||
            enable_str = var.get('enable', 'false')
 | 
			
		||||
            cb.setChecked(enable_str.lower() == 'true')
 | 
			
		||||
@ -534,6 +609,7 @@ class VarEditor(QWidget):
 | 
			
		||||
            ret_combo.currentTextChanged.connect(self.write_to_xml)
 | 
			
		||||
            short_name_edit.textChanged.connect(self.write_to_xml)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        self.write_to_xml()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -607,11 +683,11 @@ class VarEditor(QWidget):
 | 
			
		||||
 | 
			
		||||
            # Читаем переменные из таблицы (активные/изменённые)
 | 
			
		||||
            table_vars = {v['name']: v for v in self.read_table()}
 | 
			
		||||
            # Все переменные (в том числе новые, которых нет в XML)
 | 
			
		||||
            # Все переменные (в том числе новые, которых нет в таблице)
 | 
			
		||||
            all_vars_by_name = {v['name']: v for v in self.vars_list}
 | 
			
		||||
 | 
			
		||||
            # Объединённый список переменных для записи
 | 
			
		||||
            all_names = set(all_vars_by_name.keys())
 | 
			
		||||
            all_names = list(all_vars_by_name.keys())
 | 
			
		||||
            for name in all_names:
 | 
			
		||||
                v = all_vars_by_name[name]
 | 
			
		||||
                v_table = table_vars.get(name)
 | 
			
		||||
@ -633,11 +709,18 @@ class VarEditor(QWidget):
 | 
			
		||||
 | 
			
		||||
                set_sub_elem_text(var_elem, 'show_var', v.get('show_var', 'false'))
 | 
			
		||||
                set_sub_elem_text(var_elem, 'enable', v.get('enable', 'false'))
 | 
			
		||||
                set_sub_elem_text(var_elem, 'shortname', v['shortname'])
 | 
			
		||||
                set_sub_elem_text(var_elem, 'pt_type', v['pt_type'])
 | 
			
		||||
                set_sub_elem_text(var_elem, 'iq_type', v['iq_type'])
 | 
			
		||||
                set_sub_elem_text(var_elem, 'return_type', v['return_type'])
 | 
			
		||||
                set_sub_elem_text(var_elem, 'type', v['type'])
 | 
			
		||||
 | 
			
		||||
                # Тут подтягиваем из таблицы, если есть, иначе из v
 | 
			
		||||
                shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '')
 | 
			
		||||
                pt_type_val = v_table['pt_type'] if v_table and 'pt_type' in v_table else v.get('pt_type', '')
 | 
			
		||||
                iq_type_val = v_table['iq_type'] if v_table and 'iq_type' in v_table else v.get('iq_type', '')
 | 
			
		||||
                ret_type_val = v_table['return_type'] if v_table and 'return_type' in v_table else v.get('return_type', '')
 | 
			
		||||
 | 
			
		||||
                set_sub_elem_text(var_elem, 'shortname', shortname_val)
 | 
			
		||||
                set_sub_elem_text(var_elem, 'pt_type', pt_type_val)
 | 
			
		||||
                set_sub_elem_text(var_elem, 'iq_type', iq_type_val)
 | 
			
		||||
                set_sub_elem_text(var_elem, 'return_type', ret_type_val)
 | 
			
		||||
                set_sub_elem_text(var_elem, 'type', v.get('type', ''))
 | 
			
		||||
 | 
			
		||||
                # file/extern/static: из original_info, либо из v
 | 
			
		||||
                file_val = v.get('file') or original_info.get(name, {}).get('file', '')
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										34
									
								
								build/build_and_clean.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								build/build_and_clean.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
import subprocess
 | 
			
		||||
import shutil
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
# Пути
 | 
			
		||||
dist_path = os.path.abspath("./")         # текущая папка — exe будет тут
 | 
			
		||||
work_path = os.path.abspath("./build_temp")
 | 
			
		||||
spec_path = os.path.abspath("./build_temp")
 | 
			
		||||
script_dir = os.path.dirname(os.path.abspath(__file__))
 | 
			
		||||
libclang_path = os.path.join(script_dir, "libclang.dll")
 | 
			
		||||
 | 
			
		||||
# Запуск PyInstaller с нужными параметрами
 | 
			
		||||
cmd = [
 | 
			
		||||
    "pyinstaller",
 | 
			
		||||
    "--onefile",
 | 
			
		||||
    "--windowed",
 | 
			
		||||
    "--name", "DebugVarEdit",
 | 
			
		||||
    "--add-binary", f"{libclang_path};.",
 | 
			
		||||
    "--distpath", dist_path,
 | 
			
		||||
    "--workpath", work_path,
 | 
			
		||||
    "--specpath", spec_path,
 | 
			
		||||
    "./Src/setupVars_GUI.py"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
result = subprocess.run(cmd)
 | 
			
		||||
if result.returncode == 0:
 | 
			
		||||
    # Удаляем временные папки
 | 
			
		||||
    for folder in ["build_temp", "__pycache__"]:
 | 
			
		||||
        if os.path.exists(folder):
 | 
			
		||||
            shutil.rmtree(folder)
 | 
			
		||||
            
 | 
			
		||||
    print("Сборка успешно завершена!")
 | 
			
		||||
else:
 | 
			
		||||
    print("Сборка завершилась с ошибкой.")
 | 
			
		||||
							
								
								
									
										32
									
								
								debug_vars.c
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								debug_vars.c
									
									
									
									
									
								
							@ -3,33 +3,33 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
 | 
			
		||||
#include "xp_project.h"
 | 
			
		||||
#include "RS_Functions_modbus.h"
 | 
			
		||||
#include "vector.h"
 | 
			
		||||
#include "errors.h"
 | 
			
		||||
#include "RS_Functions_modbus.h"
 | 
			
		||||
#include "adc_tools.h"
 | 
			
		||||
#include "pwm_vector_regul.h"
 | 
			
		||||
#include "xp_write_xpwm_time.h"
 | 
			
		||||
#include "log_can.h"
 | 
			
		||||
#include "f281xpwm.h"
 | 
			
		||||
#include "errors.h"
 | 
			
		||||
#include "v_pwm24.h"
 | 
			
		||||
#include "f281xpwm.h"
 | 
			
		||||
#include "xp_project.h"
 | 
			
		||||
#include "rotation_speed.h"
 | 
			
		||||
#include "teta_calc.h"
 | 
			
		||||
#include "dq_to_alphabeta_cos.h"
 | 
			
		||||
#include "xp_controller.h"
 | 
			
		||||
#include "x_parallel_bus.h"
 | 
			
		||||
#include "xp_rotation_sensor.h"
 | 
			
		||||
#include "xPeriphSP6_loader.h"
 | 
			
		||||
#include "Spartan2E_Functions.h"
 | 
			
		||||
#include "x_serial_bus.h"
 | 
			
		||||
#include "xp_write_xpwm_time.h"
 | 
			
		||||
#include "log_can.h"
 | 
			
		||||
#include "pwm_vector_regul.h"
 | 
			
		||||
#include "RS_Functions.h"
 | 
			
		||||
#include "detect_phase_break2.h"
 | 
			
		||||
#include "svgen_dq.h"
 | 
			
		||||
#include "x_parallel_bus.h"
 | 
			
		||||
#include "Spartan2E_Functions.h"
 | 
			
		||||
#include "x_serial_bus.h"
 | 
			
		||||
#include "xp_controller.h"
 | 
			
		||||
#include "xp_rotation_sensor.h"
 | 
			
		||||
#include "xPeriphSP6_loader.h"
 | 
			
		||||
#include "log_to_memory.h"
 | 
			
		||||
#include "log_params.h"
 | 
			
		||||
#include "global_time.h"
 | 
			
		||||
#include "CAN_Setup.h"
 | 
			
		||||
#include "log_params.h"
 | 
			
		||||
#include "CRC_Functions.h"
 | 
			
		||||
#include "svgen_dq.h"
 | 
			
		||||
#include "pid_reg3.h"
 | 
			
		||||
#include "IQmathLib.h"
 | 
			
		||||
#include "doors_control.h"
 | 
			
		||||
@ -317,5 +317,5 @@ extern int zero_ADC[20];
 | 
			
		||||
int DebugVar_Qnt = 1;
 | 
			
		||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
 | 
			
		||||
DebugVar_t dbg_vars[] = {\
 | 
			
		||||
{(char *)&ADC1finishAddr                            , pt_int16              , iq_none               , "ADC1finishAddr"                          }, \
 | 
			
		||||
{(char *)&Bender.KOhms                              , pt_uint16             , iq_none               , "Bender.KOhms"                            }, \
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2860
									
								
								structs.xml
									
									
									
									
									
								
							
							
						
						
									
										2860
									
								
								structs.xml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user