Compare commits

...

3 Commits

Author SHA1 Message Date
3343428796 попытка перехода на python 3.12 и nutika для компиляции без зависимостей 2025-07-09 19:07:27 +03:00
f881132fa8 добавлена кнопка для открытия выходного файла
+ exe
2025-07-09 15:56:18 +03:00
4962276760 работа с таблицей перенесена в отдельный файл
сделано автозаполнение при поиске переменых
сделано правильное формирование структур, через . или ->

пофиксены мелкие фиксы
2025-07-09 15:40:16 +03:00
156 changed files with 311005 additions and 5611 deletions

Binary file not shown.

View File

@ -1,9 +1,10 @@
import re import re
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton, QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QHeaderView QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout
) )
from PySide6.QtCore import Qt from PySide6.QtGui import QKeySequence, QKeyEvent
from PySide6.QtCore import Qt, QStringListModel, QSettings
from setupVars import * from setupVars import *
from scanVars import * from scanVars import *
@ -25,9 +26,21 @@ class VariableSelectorDialog(QDialog):
self.xml_path = xml_path # сохраняем путь к xml self.xml_path = xml_path # сохраняем путь к xml
# --- Добавляем чекбокс для автодополнения ---
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
self.autocomplete_checkbox.setChecked(True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
# Восстанавливаем сохранённое состояние чекбокса, если есть
checked = self.settings.value("autocomplete_enabled", True, type=bool)
self.autocomplete_checkbox.setChecked(checked)
# При изменении состояния чекбокса сохраняем его
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
self.search_input = QLineEdit() self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Поиск по имени переменной...") self.search_input.setPlaceholderText("Поиск по имени переменной...")
self.search_input.textChanged.connect(self.filter_tree) self.search_input.textChanged.connect(self.on_search_text_changed)
self.tree = QTreeWidget() self.tree = QTreeWidget()
self.tree.setHeaderLabels(["Имя переменной", "Тип"]) self.tree.setHeaderLabels(["Имя переменной", "Тип"])
@ -51,17 +64,33 @@ class VariableSelectorDialog(QDialog):
self.btn_delete = QPushButton("Удалить выбранные") self.btn_delete = QPushButton("Удалить выбранные")
self.btn_delete.clicked.connect(self.on_delete_clicked) self.btn_delete.clicked.connect(self.on_delete_clicked)
self.completer = QCompleter()
self.completer.setCompletionMode(QCompleter.PopupCompletion) # важно!
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.completer.setWidget(self.search_input)
self.search_input.installEventFilter(self)
# Создаем горизонтальный layout для "Поиск:" и чекбокса справа
search_layout = QHBoxLayout()
label_search = QLabel("Поиск:")
search_layout.addWidget(label_search, alignment=Qt.AlignLeft)
search_layout.addStretch() # чтобы чекбокс прижался вправо
search_layout.addWidget(self.autocomplete_checkbox, alignment=Qt.AlignRight)
self.completer.activated[str].connect(self.insert_completion)
layout = QVBoxLayout() layout = QVBoxLayout()
layout.addWidget(QLabel("Поиск:")) layout.addLayout(search_layout) # заменили label и чекбокс
layout.addWidget(self.search_input) layout.addWidget(self.search_input)
layout.addWidget(self.tree) layout.addWidget(self.tree)
layout.addWidget(self.btn_add) layout.addWidget(self.btn_add)
layout.addWidget(self.btn_delete) # Кнопка удаления layout.addWidget(self.btn_delete)
self.setLayout(layout) self.setLayout(layout)
self.populate_tree() self.populate_tree()
def add_tree_item_recursively(self, parent, var): def add_tree_item_recursively(self, parent, var):
""" """
Рекурсивно добавляет переменную и её дочерние поля в дерево. Рекурсивно добавляет переменную и её дочерние поля в дерево.
@ -113,7 +142,7 @@ class VariableSelectorDialog(QDialog):
def filter_tree(self): def filter_tree(self):
text = self.search_input.text().strip().lower() text = self.search_input.text().strip().lower()
path_parts = text.split('.') if text else [] path_parts = self.split_path(text) if text else []
def hide_all(item): def hide_all(item):
item.setHidden(True) item.setHidden(True)
@ -121,7 +150,7 @@ class VariableSelectorDialog(QDialog):
hide_all(item.child(i)) hide_all(item.child(i))
def path_matches_search(name, search_parts): def path_matches_search(name, search_parts):
name_parts = name.lower().split('.') name_parts = self.split_path(name.lower())
if len(name_parts) < len(search_parts): if len(name_parts) < len(search_parts):
return False return False
for sp, np in zip(search_parts, name_parts): for sp, np in zip(search_parts, name_parts):
@ -156,11 +185,189 @@ class VariableSelectorDialog(QDialog):
return matched or matched_any_child return matched or matched_any_child
for i in range(self.tree.topLevelItemCount()): # Если в поиске нет точки — особая логика для первого уровня
item = self.tree.topLevelItem(i) if '.' not in text and '->' not in text and text != '':
hide_all(item) for i in range(self.tree.topLevelItemCount()):
show_matching_path(item, 0) item = self.tree.topLevelItem(i)
name = item.text(0).lower()
if text in name:
item.setHidden(False)
item.setExpanded(False) # НЕ раскрываем потомков
else:
hide_all(item)
item.setHidden(True)
else:
# Обычная логика с поиском по пути
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
hide_all(item)
show_matching_path(item, 0)
def update_completions(self, text = None):
if text is None:
text = self.search_input.text().strip()
else:
text = text.strip()
parts = self.split_path(text)
path_parts = parts[:-1]
prefix = parts[-1].lower() if not text.endswith(('.', '>')) else ''
# Если путь есть (например: project.adc или project.adc.), ищем внутри него
search_deep = len(path_parts) > 0
def find_path_items(path_parts):
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
for part in path_parts:
part_lower = part.lower()
matched = []
for item in items:
# Берём последний фрагмент имени item, разделённого точками
item_name_part = self.split_path(item.text(0))[-1].lower()
if item_name_part == part_lower:
matched.append(item)
if not matched:
return []
items = []
# Собираем детей для следующего уровня поиска
for node in matched:
for i in range(node.childCount()):
items.append(node.child(i))
return matched
if not search_deep:
# Без точки — ищем только в топ-уровне, фильтруя по prefix
items = []
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
name_part = self.split_path(item.text(0))[-1].lower()
if name_part.startswith(prefix):
items.append(item)
completions = [item.text(0) for item in items]
else:
# С точкой — углубляемся по пути и показываем имена детей
if len(path_parts) == 0:
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
else:
items = find_path_items(path_parts)
completions = []
for item in items:
for i in range(item.childCount()):
child = item.child(i)
name_part = self.split_path(child.text(0))[-1].lower()
if prefix == '' or name_part.startswith(prefix):
completions.append(child.text(0))
self.completer.setModel(QStringListModel(completions))
return completions
def eventFilter(self, obj, event):
if obj == self.search_input and isinstance(event, QKeyEvent):
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
completions = self.update_completions()
self.completer.complete()
text = self.search_input.text().strip()
if len(completions) == 1 and completions[0].lower() == text.lower():
# Найдем узел с таким именем
def find_exact_item(name):
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
while stack:
node = stack.pop()
if node.text(0).lower() == name.lower():
return node
for i in range(node.childCount()):
stack.append(node.child(i))
return None
node = find_exact_item(completions[0])
if node and node.childCount() > 0:
# Используем первую подсказку, чтобы определить нужный разделитель
completions = self.update_completions(text + '.')
suggestion = completions[0]
# Ищем, какой символ идёт после текущего текста
separator = '.'
if suggestion.startswith(text):
rest = suggestion[len(text):]
if rest.startswith('->'):
separator = '->'
elif rest.startswith('.'):
separator = '.'
self.search_input.setText(text + separator)
completions = self.update_completions()
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
return True
# Иначе просто показываем подсказки
self.completer.setModel(QStringListModel(completions))
if completions:
self.completer.complete()
return True
return super().eventFilter(obj, event)
# Функция для поиска узла с полным именем
def find_node_by_fullname(self, name):
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
while stack:
node = stack.pop()
if node.text(0).lower() == name.lower():
return node
for i in range(node.childCount()):
stack.append(node.child(i))
return None
def insert_completion(self, text):
node = self.find_node_by_fullname(text)
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
# Определяем разделитель по имени первого ребёнка
child_name = node.child(0).text(0)
if child_name.startswith(text + '->'):
text += '->'
else:
text += '.'
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
self.update_completions()
self.completer.complete()
else:
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
def on_search_text_changed(self, text):
if self.autocomplete_checkbox.isChecked():
completions = self.update_completions(text)
node = self.find_node_by_fullname(text)
should_show = False
if completions:
if len(completions) > 1:
should_show = True
elif len(completions) == 1:
single_node = self.find_node_by_fullname(completions[0])
if single_node and single_node.childCount() > 0:
should_show = True
elif node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
should_show = True
if should_show:
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
self.filter_tree()
def on_add_clicked(self): def on_add_clicked(self):
self.selected_names = [] self.selected_names = []
@ -206,16 +413,63 @@ class VariableSelectorDialog(QDialog):
self.accept() self.accept()
def on_delete_clicked(self): def on_delete_clicked(self):
# Деактивируем (удаляем из видимых) выбранные переменные selected_names = self._get_selected_var_names()
for item in self.tree.selectedItems(): if not selected_names:
name = item.text(0) return
if not name:
continue # Обновляем var_map и all_vars
for name in selected_names:
if name in self.var_map: if name in self.var_map:
var = self.var_map[name] self.var_map[name]['show_var'] = 'false'
var['show_var'] = 'false' self.var_map[name]['enable'] = 'false'
var['enable'] = 'false'
for v in self.all_vars:
if v['name'] == name:
v['show_var'] = 'false'
v['enable'] = 'false'
break
# Проверка пути к XML
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
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
def set_text(tag, value):
el = var_elem.find(tag)
if el is None:
el = ET.SubElement(var_elem, tag)
el.text = value
set_text('show_var', 'false')
set_text('enable', 'false')
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(self.xml_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
self.populate_tree()
self.accept() self.accept()
@ -231,16 +485,11 @@ class VariableSelectorDialog(QDialog):
super().keyPressEvent(event) super().keyPressEvent(event)
def delete_selected_vars(self): def delete_selected_vars(self):
# Деактивируем (удаляем из видимых) выбранные переменные selected_names = self._get_selected_var_names()
for item in self.tree.selectedItems(): if not selected_names:
name = item.text(0) return
if not name:
continue
if name in self.var_map:
var = self.var_map[name]
var['show_var'] = 'false'
var['enable'] = 'false'
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path: if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide6.QtWidgets import QMessageBox from PySide6.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.") QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
@ -249,31 +498,47 @@ class VariableSelectorDialog(QDialog):
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
tree = ET.parse(self.xml_path) tree = ET.parse(self.xml_path)
root = tree.getroot() root = tree.getroot()
if root is None: if root is None:
return return
vars_section = root.find('variables') vars_section = root.find('variables')
if vars_section is None: if vars_section is None:
return # Нет секции variables — ничего удалять return
selected_names = [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
removed_any = False removed_any = False
for var_elem in vars_section.findall('var'): for var_elem in list(vars_section.findall('var')):
name = var_elem.attrib.get('name') name = var_elem.attrib.get('name')
if name in selected_names: if name in selected_names:
vars_section.remove(var_elem) vars_section.remove(var_elem)
removed_any = True removed_any = True
if name in self.var_map: self.var_map.pop(name, None)
del self.var_map[name]
# Удаляем элементы из списка на месте
self.all_vars[:] = [v for v in self.all_vars if v['name'] != name]
# Удаляем из all_vars (глобально)
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
if removed_any: if removed_any:
ET.indent(tree, space=" ", level=0) # Преобразуем дерево в строку
tree.write(self.xml_path, encoding='utf-8', xml_declaration=True) rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(self.xml_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
self.populate_tree() self.populate_tree()
self.filter_tree()
def _get_selected_var_names(self):
return [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
def save_checkbox_state(self):
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
def split_path(self, path):
# Разбиваем по точке или по -> (учитываем, что -> длиной 2 символа)
return re.split(r'\.|->', path)

184
Src/VariableTable.py Normal file
View File

@ -0,0 +1,184 @@
from PySide6.QtWidgets import (
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
QAbstractItemView, QHeaderView
)
from PySide6.QtCore import Qt
from enum import IntEnum
from generateVars import type_map
class rows(IntEnum):
No = 0
include = 1
name = 2
type = 3
pt_type = 4
iq_type = 5
ret_type = 6
short_name = 7
class VariableTableWidget(QTableWidget):
def __init__(self, parent=None):
super().__init__(0, 8, parent)
# Таблица переменных
self.setHorizontalHeaderLabels([
'', # новый столбец
'En',
'Name',
'Origin Type',
'Pointer Type',
'IQ Type',
'Return Type',
'Short Name'
])
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
self.iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
header = self.horizontalHeader()
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
for col in range(self.columnCount()):
if col == self.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch)
else:
header.setSectionResizeMode(col, QHeaderView.Interactive)
parent_widget = self.parentWidget()
# Сделаем колонки с номерами фиксированной ширины
self.setColumnWidth(rows.No, 30)
self.setColumnWidth(rows.include, 30)
self.setColumnWidth(rows.pt_type, 85)
self.setColumnWidth(rows.iq_type, 85)
self.setColumnWidth(rows.ret_type, 85)
self.setColumnWidth(rows.name, 300)
self.setColumnWidth(rows.type, 100)
self._resizing = False
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
def populate(self, vars_list, structs, on_change_callback):
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
self.setRowCount(len(filtered_vars))
self.verticalHeader().setVisible(False)
for row, var in enumerate(filtered_vars):
# №
no_item = QTableWidgetItem(str(row))
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.setItem(row, rows.No, no_item)
# Enable
cb = QCheckBox()
cb.setChecked(var.get('enable', 'false') == 'true')
cb.stateChanged.connect(on_change_callback)
self.setCellWidget(row, rows.include, cb)
# Name
name_edit = QLineEdit(var['name'])
if var['type'] in structs:
completer = QCompleter(structs[var['type']].keys())
completer.setCaseSensitivity(Qt.CaseInsensitive)
name_edit.setCompleter(completer)
name_edit.textChanged.connect(on_change_callback)
self.setCellWidget(row, rows.name, name_edit)
# Origin Type (readonly)
origin_item = QTableWidgetItem(var.get('type', ''))
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.setItem(row, rows.type, origin_item)
# pt_type
pt_combo = QComboBox()
pt_combo.addItems(self.display_type_options)
value = var['pt_type'].replace('pt_', '')
if value not in self.display_type_options:
pt_combo.addItem(value)
pt_combo.setCurrentText(value)
pt_combo.currentTextChanged.connect(on_change_callback)
self.setCellWidget(row, rows.pt_type, pt_combo)
# iq_type
iq_combo = QComboBox()
iq_combo.addItems(self.iq_types)
value = var['iq_type'].replace('t_', '')
if value not in self.iq_types:
iq_combo.addItem(value)
iq_combo.setCurrentText(value)
iq_combo.currentTextChanged.connect(on_change_callback)
self.setCellWidget(row, rows.iq_type, iq_combo)
# return_type
ret_combo = QComboBox()
ret_combo.addItems(self.iq_types)
ret_combo.setCurrentText(var.get('return_type', ''))
ret_combo.currentTextChanged.connect(on_change_callback)
self.setCellWidget(row, rows.ret_type, ret_combo)
# short_name
short_name_edit = QLineEdit(var.get('shortname', var['name']))
short_name_edit.textChanged.connect(on_change_callback)
self.setCellWidget(row, rows.short_name, short_name_edit)
def read_data(self):
result = []
for row in range(self.rowCount()):
cb = self.cellWidget(row, rows.include)
name = self.cellWidget(row, rows.name).text()
pt = self.cellWidget(row, rows.pt_type).currentText()
iq = self.cellWidget(row, rows.iq_type).currentText()
ret = self.cellWidget(row, rows.ret_type).currentText()
shortname = self.cellWidget(row, rows.short_name).text()
origin_type = self.item(row, rows.type).text()
result.append({
'show_var': True,
'enable': cb.isChecked(),
'name': name,
'pt_type': f'pt_{pt}',
'iq_type': iq,
'return_type': ret,
'shortname': shortname,
'type': origin_type,
})
return result
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.columnCount():
# Если правая колока - нет соседа, ограничиваем минимальную ширину
if newSize < min_width:
self._resizing = True
self.setColumnWidth(logicalIndex, min_width)
self._resizing = False
return
self._resizing = True
try:
right_width = self.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.setColumnWidth(logicalIndex, newSize)
self.setColumnWidth(right_index, new_right_width)
finally:
self._resizing = False

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,6 +8,7 @@ import os
import re import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
from xml.dom import minidom
import argparse import argparse
@ -214,8 +215,16 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
added_count += 1 added_count += 1
if added_count > 0: if added_count > 0:
ET.indent(tree, space=" ", level=0) # Преобразуем дерево в строку
tree.write(xml_full_path, encoding="utf-8", xml_declaration=True) rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(xml_full_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
print(f"[INFO] В XML добавлено новых переменных: {added_count}") print(f"[INFO] В XML добавлено новых переменных: {added_count}")
return True return True
else: else:
@ -330,12 +339,13 @@ def generate_vars_file(proj_path, xml_path, output_dir):
# Дополнительные поля, например комментарий # Дополнительные поля, например комментарий
comment = info.get("comment", "") comment = info.get("comment", "")
short_name = info.get("shortname", f'"{vname}"')
if pt_type not in ('pt_struct', 'pt_union'): if pt_type not in ('pt_struct', 'pt_union'):
formated_name = f'"{vname}"' formated_name = f'"{vname}"'
# Добавим комментарий после записи, если он есть # Добавим комментарий после записи, если он есть
comment_str = f' // {comment}' if comment else '' comment_str = f' // {comment}' if comment else ''
line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {formated_name:<42}}}, \\{comment_str}' line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {short_name:<42}}}, \\{comment_str}'
new_debug_vars[vname] = line new_debug_vars[vname] = line
else: else:

View File

@ -7,11 +7,14 @@ def strip_single_line_comments(code):
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE) return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
def read_file_try_encodings(filepath): def read_file_try_encodings(filepath):
if not os.path.isfile(filepath):
# Файл не существует — просто вернуть пустую строку или None
return "", None
for enc in ['utf-8', 'cp1251']: for enc in ['utf-8', 'cp1251']:
try: try:
with open(filepath, 'r', encoding=enc) as f: with open(filepath, 'r', encoding=enc) as f:
content = f.read() content = f.read()
content = strip_single_line_comments(content) # <=== ВАЖНО content = strip_single_line_comments(content)
return content, enc return content, enc
except UnicodeDecodeError: except UnicodeDecodeError:
continue continue
@ -38,6 +41,8 @@ def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
processed_files.add(norm_path) processed_files.add(norm_path)
content, _ = read_file_try_encodings(cfile) content, _ = read_file_try_encodings(cfile)
if content is None:
continue
includes = include_pattern.findall(content) includes = include_pattern.findall(content)
for inc in includes: for inc in includes:
# Ищем полный путь к include-файлу в include_dirs # Ищем полный путь к include-файлу в include_dirs
@ -61,9 +66,9 @@ def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
return include_files return include_files
def parse_makefile(makefile_path): def parse_makefile(makefile_path, proj_path):
makefile_dir = os.path.dirname(makefile_path) makefile_dir = os.path.dirname(makefile_path)
project_root = os.path.dirname(makefile_dir) # поднялись из Debug project_root = proj_path
with open(makefile_path, 'r', encoding='utf-8') as f: with open(makefile_path, 'r', encoding='utf-8') as f:
lines = f.readlines() lines = f.readlines()
@ -115,6 +120,8 @@ def parse_makefile(makefile_path):
continue continue
if "v120" in obj_path: if "v120" in obj_path:
continue continue
if "v100" in obj_path:
continue
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"): if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/") rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
@ -129,6 +136,10 @@ def parse_makefile(makefile_path):
else: else:
c_path = abs_path c_path = abs_path
# Проверяем существование файла, если нет — пропускаем
if not os.path.isfile(c_path):
continue
# Сохраняем только .c файлы # Сохраняем только .c файлы
if c_path.lower().endswith(".c"): if c_path.lower().endswith(".c"):
c_files.append(c_path) c_files.append(c_path)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -812,7 +812,7 @@ Usage example:
print(f"Error: Makefile path '{makefile_path}' does not exist.") print(f"Error: Makefile path '{makefile_path}' does not exist.")
sys.exit(1) sys.exit(1)
c_files, h_files, include_dirs = parse_makefile(makefile_path) c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs) vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs) typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
@ -855,7 +855,7 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
if not os.path.isfile(makefile_path): if not os.path.isfile(makefile_path):
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.") raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
c_files, h_files, include_dirs = parse_makefile(makefile_path) c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs) vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs) typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)

View File

@ -63,6 +63,21 @@ def parse_vars(filename, typedef_map=None):
iq_type = var.findtext('iq_type') iq_type = var.findtext('iq_type')
if not iq_type: if not iq_type:
iq_type = get_iq_define(var_type) iq_type = get_iq_define(var_type)
# Записываем iq_type в XML
iq_type_elem = var.find('iq_type')
if iq_type_elem is None:
iq_type_elem = ET.SubElement(var, 'iq_type')
iq_type_elem.text = iq_type
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
# Записываем pt_type в XML
pt_type_elem = var.find('pt_type')
if pt_type_elem is None:
pt_type_elem = ET.SubElement(var, 'pt_type')
pt_type_elem.text = pt_type
vars_list.append({ vars_list.append({
'name': name, 'name': name,
@ -78,6 +93,17 @@ def parse_vars(filename, typedef_map=None):
'static': var.findtext('static', 'false') == 'true', 'static': var.findtext('static', 'false') == 'true',
}) })
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
# Парсим и форматируем с отступами
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(filename, "w", encoding="utf-8") as f:
f.write(pretty_xml)
return vars_list return vars_list
@ -159,6 +185,9 @@ def safe_parse_xml(xml_path):
except Exception as e: except Exception as e:
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}") print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
return None, None return None, None
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0): def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
if depth > 10: if depth > 10:
return [] return []
@ -178,7 +207,15 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
if field_name == 'type': if field_name == 'type':
continue continue
full_name = f"{prefix}.{field_name}" # Определяем разделитель между prefix и полем
if prefix.endswith('*'):
separator = '->'
# Для красоты можно убрать пробелы у указателя
# например, если prefix="ptr*" -> "ptr->field"
full_name = f"{prefix[:-1]}{separator}{field_name}"
else:
separator = '.'
full_name = f"{prefix}{separator}{field_name}"
if isinstance(field_value, dict): if isinstance(field_value, dict):
# Если вложенная структура — берем её имя типа из поля 'type' или пустую строку # Если вложенная структура — берем её имя типа из поля 'type' или пустую строку
@ -187,10 +224,14 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
'name': full_name, 'name': full_name,
'type': type_name, 'type': type_name,
'pt_type': '', 'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'), 'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'), 'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'), 'static': var_attrs.get('static'),
} }
if '*' in type_name and not full_name.endswith('*'):
full_name += '*'
# Рекурсивно раскрываем вложенные поля # Рекурсивно раскрываем вложенные поля
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1) subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren: if subchildren:
@ -205,6 +246,8 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
'name': full_name, 'name': full_name,
'type': field_value, 'type': field_value,
'pt_type': '', 'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'), 'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'), 'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'), 'static': var_attrs.get('static'),
@ -215,6 +258,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
return children return children
def expand_vars(vars_list, structs, typedefs): def expand_vars(vars_list, structs, typedefs):
""" """
Раскрывает структуры и массивы структур в деревья. Раскрывает структуры и массивы структур в деревья.
@ -228,6 +272,11 @@ def expand_vars(vars_list, structs, typedefs):
fields = structs.get(base_type) fields = structs.get(base_type)
if pt_type.startswith('pt_ptr_') and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var)
expanded.append(new_var)
if pt_type.startswith('pt_arr_') and isinstance(fields, dict): if pt_type.startswith('pt_arr_') and isinstance(fields, dict):
new_var = var.copy() new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var) new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)

View File

@ -12,27 +12,18 @@ from scanVars import run_scan
from generateVars import run_generate from generateVars import run_generate
from setupVars import * from setupVars import *
from VariableSelector import * from VariableSelector import *
from VariableTable import VariableTableWidget, rows
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem, QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton, QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit, QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView
) )
from PySide6.QtGui import QTextCursor, QKeyEvent from PySide6.QtGui import QTextCursor, QKeyEvent
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QTimer from PySide6.QtCore import Qt, QProcess, QObject, Signal, QSettings
class rows(IntEnum):
No = 0
include = 1
name = 2
type = 3
pt_type = 4
iq_type = 5
ret_type = 6
short_name = 7
class EmittingStream(QObject): class EmittingStream(QObject):
text_written = Signal(str) text_written = Signal(str)
@ -54,11 +45,12 @@ class EmittingStream(QObject):
self._buffer = "" self._buffer = ""
class ProcessOutputWindowDummy(QWidget): class ProcessOutputWindowDummy(QDialog):
def __init__(self, on_done_callback): def __init__(self, on_done_callback):
super().__init__() super().__init__()
self.setWindowTitle("Поиск переменных...") self.setWindowTitle("Поиск переменных...")
self.resize(600, 400) self.resize(600, 400)
self.setModal(True) # сделаем окно модальным
self.layout = QVBoxLayout(self) self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit() self.output_edit = QTextEdit()
@ -75,7 +67,7 @@ class ProcessOutputWindowDummy(QWidget):
def __handle_done(self): def __handle_done(self):
if self._on_done_callback: if self._on_done_callback:
self._on_done_callback() self._on_done_callback()
self.close() self.accept() # закрыть диалог
def append_text(self, text): def append_text(self, text):
cursor = self.output_edit.textCursor() cursor = self.output_edit.textCursor()
@ -161,20 +153,6 @@ class VarEditor(QWidget):
self.btn_update_vars = QPushButton("Обновить данные о переменных") self.btn_update_vars = QPushButton("Обновить данные о переменных")
self.btn_update_vars.clicked.connect(self.update_vars_data) self.btn_update_vars.clicked.connect(self.update_vars_data)
# Таблица переменных
self.table = QTableWidget(len(self.vars_list), 8)
self.table.setHorizontalHeaderLabels([
'', # новый столбец
'En',
'Name',
'Origin Type',
'Pointer Type',
'IQ Type',
'Return Type',
'Short Name'
])
self.table.setEditTriggers(QAbstractItemView.AllEditTriggers)
# Кнопка сохранения # Кнопка сохранения
btn_save = QPushButton("Build") btn_save = QPushButton("Build")
btn_save.clicked.connect(self.save_build) btn_save.clicked.connect(self.save_build)
@ -183,7 +161,11 @@ class VarEditor(QWidget):
self.btn_add_vars = QPushButton("Add Variables") self.btn_add_vars = QPushButton("Add Variables")
self.btn_add_vars.clicked.connect(self.__open_variable_selector) self.btn_add_vars.clicked.connect(self.__open_variable_selector)
# Кнопка открыть output-файл с выбором программы
btn_open_output = QPushButton("Открыть Output File...")
btn_open_output.clicked.connect(self.__open_output_file_with_program)
# Таблица
self.table = VariableTableWidget()
# Основной layout # Основной layout
layout = QVBoxLayout() layout = QVBoxLayout()
layout.addLayout(xml_layout) layout.addLayout(xml_layout)
@ -194,69 +176,11 @@ class VarEditor(QWidget):
layout.addWidget(self.btn_add_vars) layout.addWidget(self.btn_add_vars)
layout.addLayout(source_output_layout) layout.addLayout(source_output_layout)
layout.addWidget(btn_save) layout.addWidget(btn_save)
layout.addWidget(btn_open_output)
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) 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): def get_xml_path(self):
xml_path = self.xml_output_edit.text().strip() xml_path = self.xml_output_edit.text().strip()
@ -416,18 +340,17 @@ class VarEditor(QWidget):
else: else:
self.makefile_path = None self.makefile_path = None
if not self.structs_path: # --- structs_path из атрибута ---
# --- structs_path из атрибута --- structs_path = root.attrib.get('structs_path', '').strip()
structs_path = root.attrib.get('structs_path', '').strip() structs_path_full = make_absolute_path(structs_path, self.proj_path)
structs_path_full = make_absolute_path(structs_path, self.proj_path) if structs_path_full and os.path.isfile(structs_path_full):
if structs_path_full and os.path.isfile(structs_path_full): self.structs_path = structs_path_full
self.structs_path = structs_path_full self.structs, self.typedef_map = parse_structs(structs_path_full)
self.structs, self.typedef_map = parse_structs(structs_path_full) else:
else: self.structs_path = None
self.structs_path = None
self.vars_list = parse_vars(self.xml_path, self.typedef_map) self.vars_list = parse_vars(self.xml_path, self.typedef_map)
self.update_table() self.table.populate(self.vars_list, self.structs, self.write_to_xml)
except Exception as e: except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}") QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
@ -528,7 +451,7 @@ class VarEditor(QWidget):
v['show_var'] = 'false' v['show_var'] = 'false'
break break
self.update_table() self.table.populate(self.vars_list, self.structs, self.write_to_xml)
def __open_variable_selector(self): def __open_variable_selector(self):
@ -542,102 +465,6 @@ class VarEditor(QWidget):
self.update() self.update()
def update_table(self):
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
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')
self.table.setCellWidget(row, rows.include, cb)
name_edit = QLineEdit(var['name'])
if var['type'] in self.structs:
completer = QCompleter(self.structs[var['type']].keys())
completer.setCaseSensitivity(Qt.CaseInsensitive)
name_edit.setCompleter(completer)
self.table.setCellWidget(row, rows.name, name_edit)
# Type (origin)
origin_type = var.get('type', '').strip()
origin_item = QTableWidgetItem(origin_type)
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # read-only
self.table.setItem(row, rows.type, origin_item)
pt_type_combo = QComboBox()
pt_type_combo.addItems(self.display_type_options)
internal_type = var['pt_type'].replace('pt_', '')
if internal_type in self.display_type_options:
pt_type_combo.setCurrentText(internal_type)
else:
pt_type_combo.addItem(internal_type)
pt_type_combo.setCurrentText(internal_type)
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
iq_combo = QComboBox()
iq_combo.addItems(iq_types)
iq_type = var['iq_type'].replace('t_', '')
if iq_type in iq_types:
iq_combo.setCurrentText(iq_type)
else:
iq_combo.addItem(iq_type)
iq_combo.setCurrentText(iq_type)
self.table.setCellWidget(row, rows.iq_type, iq_combo)
ret_combo = QComboBox()
ret_combo.addItems(iq_types)
self.table.setCellWidget(row, rows.ret_type, ret_combo)
short_name_edit = QLineEdit(var['name'])
self.table.setCellWidget(row, rows.short_name, short_name_edit)
cb.stateChanged.connect(self.write_to_xml)
name_edit.textChanged.connect(self.write_to_xml)
pt_type_combo.currentTextChanged.connect(self.write_to_xml)
iq_combo.currentTextChanged.connect(self.write_to_xml)
ret_combo.currentTextChanged.connect(self.write_to_xml)
short_name_edit.textChanged.connect(self.write_to_xml)
self.write_to_xml()
def read_table(self):
vars_data = []
for row in range(self.table.rowCount()):
cb = self.table.cellWidget(row, rows.include)
name_edit = self.table.cellWidget(row, rows.name)
pt_type_combo = self.table.cellWidget(row, rows.pt_type)
iq_combo = self.table.cellWidget(row, rows.iq_type)
ret_combo = self.table.cellWidget(row, rows.ret_type)
short_name_edit = self.table.cellWidget(row, rows.short_name)
origin_item = self.table.item(row, rows.type)
vars_data.append({
'show_var': True,
'enable': cb.isChecked() if cb else False,
'name': name_edit.text() if name_edit else '',
'pt_type': 'pt_' + pt_type_combo.currentText() if pt_type_combo else '',
'iq_type': iq_combo.currentText() if iq_combo else '',
'return_type': ret_combo.currentText() if ret_combo else '',
'shortname': short_name_edit.text() if short_name_edit else '',
'type': origin_item.text() if origin_item else '',
})
return vars_data
def write_to_xml(self): def write_to_xml(self):
self.update_all_paths() self.update_all_paths()
@ -682,7 +509,7 @@ class VarEditor(QWidget):
} }
# Читаем переменные из таблицы (активные/изменённые) # Читаем переменные из таблицы (активные/изменённые)
table_vars = {v['name']: v for v in self.read_table()} table_vars = {v['name']: v for v in self.table.read_data()}
# Все переменные (в том числе новые, которых нет в таблице) # Все переменные (в том числе новые, которых нет в таблице)
all_vars_by_name = {v['name']: v for v in self.vars_list} all_vars_by_name = {v['name']: v for v in self.vars_list}
@ -707,8 +534,11 @@ class VarEditor(QWidget):
el = ET.SubElement(parent, tag) el = ET.SubElement(parent, tag)
el.text = str(text) el.text = str(text)
set_sub_elem_text(var_elem, 'show_var', v.get('show_var', 'false')) show_var_val = str(v.get('show_var', 'false')).lower()
set_sub_elem_text(var_elem, 'enable', v.get('enable', 'false')) enable_val = str(v_table['enable'] if v_table and 'enable' in v_table else v.get('enable', 'false')).lower()
set_sub_elem_text(var_elem, 'show_var', show_var_val)
set_sub_elem_text(var_elem, 'enable', enable_val)
# Тут подтягиваем из таблицы, если есть, иначе из v # Тут подтягиваем из таблицы, если есть, иначе из v
shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '') shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '')
@ -731,13 +561,40 @@ class VarEditor(QWidget):
set_sub_elem_text(var_elem, 'extern', extern_val) set_sub_elem_text(var_elem, 'extern', extern_val)
set_sub_elem_text(var_elem, 'static', static_val) set_sub_elem_text(var_elem, 'static', static_val)
# Преобразуем дерево в строку
rough_string = ET.tostring(root, encoding="utf-8")
ET.indent(tree, space=" ", level=0) # Парсим и форматируем с отступами
tree.write(self.xml_path, encoding='utf-8', xml_declaration=True) reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
# Записываем в файл
with open(self.xml_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
except Exception as e: except Exception as e:
print(f"Ошибка при сохранении XML: {e}") print(f"Ошибка при сохранении XML: {e}")
def __open_output_file_with_program(self):
output_path = self.get_output_path()
if not output_path:
QMessageBox.warning(self, "Ошибка", "Путь к output-файлу не задан.")
return
output_file = os.path.join(output_path, "debug_vars.c")
if not os.path.isfile(output_file):
QMessageBox.warning(self, "Ошибка", f"Файл не найден:\n{output_file}")
return
try:
# Открыть стандартное окно Windows "Открыть с помощью..."
subprocess.run([
"rundll32.exe",
"shell32.dll,OpenAs_RunDLL",
output_file
], shell=False)
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось открыть диалог 'Открыть с помощью':\n{e}")

View File

@ -1,34 +1,119 @@
import subprocess import subprocess
import shutil import shutil
import os import os
import PySide6
# Пути USE_NUITKA = True # переключатель: True — сборка через Nuitka, False — через PyInstaller
dist_path = os.path.abspath("./") # текущая папка — exe будет тут
work_path = os.path.abspath("./build_temp") pyside6_path = os.path.dirname(PySide6.__file__)
spec_path = os.path.abspath("./build_temp") plugins_platforms_path = os.path.join(pyside6_path, "plugins", "platforms")
dist_path = os.path.abspath(".") # итоговая папка с exe
work_path = os.path.abspath("./build_temp") # временная папка для PyInstaller
spec_path = os.path.abspath("./build_temp") # папка exдля spec-файлов PyInstaller
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
libclang_path = os.path.join(script_dir, "libclang.dll") libclang_path = os.path.join(script_dir, "libclang.dll")
libssl_orig = os.path.join(script_dir, "libssl-3-x64.dll")
libcrypto_orig = os.path.join(script_dir, "libcrypto-3-x64.dll")
# Запуск PyInstaller с нужными параметрами # Собираем бинарники и данные для PyInstaller
cmd = [ add_binary_list = [f"{libclang_path};."]
"pyinstaller",
"--onefile", if os.path.exists(libssl_orig):
"--windowed", add_binary_list.append(f"{libssl_orig};libssl.dll")
"--name", "DebugVarEdit", else:
"--add-binary", f"{libclang_path};.", print("WARNING: libssl-3-x64.dll не найден, OpenSSL не будет включён")
"--distpath", dist_path,
"--workpath", work_path, if os.path.exists(libcrypto_orig):
"--specpath", spec_path, add_binary_list.append(f"{libcrypto_orig};libcrypto.dll")
"./Src/setupVars_GUI.py" else:
] print("WARNING: libcrypto-3-x64.dll не найден, OpenSSL не будет включён")
from PyInstaller.utils.hooks import collect_data_files
datas = []
datas += collect_data_files('PySide6', includes=['plugins/platforms/*'])
datas += collect_data_files('PySide6', includes=['plugins/styles/*'])
datas += collect_data_files('PySide6', includes=['plugins/imageformats/*'])
add_data_list = [f"{src};{dest}" for src, dest in datas]
script_path = "./Src/setupVars_GUI.py"
output_name = "DebugVarEdit"
env = os.environ.copy()
env["CC"] = r"C:\Users\I\AppData\Local\Nuitka\Nuitka\Cache\DOWNLOADS\gcc\x86_64\14.2.0posix-19.1.1-12.0.0-msvcrt-r2\bin\gcc.exe"
env["CXX"] = r"C:\Users\I\AppData\Local\Nuitka\Nuitka\Cache\DOWNLOADS\gcc\x86_64\14.2.0posix-19.1.1-12.0.0-msvcrt-r2\bin\g++.exe"
if not USE_NUITKA:
# PyInstaller команда
cmd = [
"pyinstaller",
"--onefile",
"--name", output_name,
*[item for add_bin in add_binary_list for item in ("--add-binary", add_bin)],
*[item for add_dat in add_data_list for item in ("--add-data", add_dat)],
"--distpath", dist_path,
"--workpath", work_path,
"--specpath", spec_path,
"--hidden-import", "PySide6.QtWidgets",
"--hidden-import", "PySide6.QtGui",
"--hidden-import", "PySide6.QtCore",
"--windowed",
script_path
]
else:
# Nuitka команда
# Формируем --include-data-dir для плагинов PySide6 (datas как в PyInstaller)
# Было — неправильный способ: передаёт файлы
plugin_dirs = set()
for src, dest in datas:
src_dir = os.path.dirname(src)
dest_root = dest.split("/", 1)[0] if "/" in dest else dest
plugin_dirs.add((src_dir, dest_root))
include_data_dirs = [
f"--include-data-dir={src}={dest}" for src, dest in plugin_dirs
]
include_data_files = []
for dll_path, dll_name in [(libclang_path, "libclang.dll"),
(libssl_orig, "libssl.dll"),
(libcrypto_orig, "libcrypto.dll")]:
if os.path.exists(dll_path):
include_data_files.append(f"--include-data-file={dll_path}={dll_name}")
cmd = [
"python", "-m", "nuitka",
"--standalone", # полностью независимый билд (аналог PyInstaller --onefile/--onedir)
"--onefile", # упаковать в один exe (экспериментально)
"--enable-plugin=pyside6",
"--windows-console-mode=disable",
f"--output-dir={dist_path}",
f"--output-filename={output_name}.exe",
] + include_data_dirs + [
script_path
]
# Удаляем временные папки, если они у тебя есть
temp_dirs = ["build_temp", "__pycache__"] # добавь свои папки с временными файлами
for d in temp_dirs:
if os.path.exists(d):
shutil.rmtree(d)
print("Выполняется сборка:")
print(" ".join(cmd))
result = subprocess.run(cmd, env=env)
result = subprocess.run(cmd)
if result.returncode == 0: if result.returncode == 0:
# Удаляем временные папки # Чистим временные папки, только для PyInstaller (для Nuitka обычно не нужно)
for folder in ["build_temp", "__pycache__"]: if not USE_NUITKA:
if os.path.exists(folder): for folder in ["build_temp", "__pycache__"]:
shutil.rmtree(folder) if os.path.exists(folder):
shutil.rmtree(folder)
print("Сборка успешно завершена!") print("Сборка успешно завершена!")
else: else:
print("Сборка завершилась с ошибкой.") print("Сборка завершилась с ошибкой.")

BIN
build/libcrypto-3-x64.dll Normal file

Binary file not shown.

BIN
build/libssl-3-x64.dll Normal file

Binary file not shown.

View File

@ -3,34 +3,34 @@
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì // Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
#include "vector.h"
#include "RS_Functions_modbus.h" #include "RS_Functions_modbus.h"
#include "adc_tools.h" #include "vector.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 "dq_to_alphabeta_cos.h"
#include "xp_write_xpwm_time.h" #include "teta_calc.h"
#include "log_can.h" #include "v_pwm24.h"
#include "errors.h"
#include "pwm_vector_regul.h" #include "pwm_vector_regul.h"
#include "xp_project.h"
#include "xp_write_xpwm_time.h"
#include "rotation_speed.h"
#include "f281xpwm.h"
#include "adc_tools.h"
#include "log_can.h"
#include "RS_Functions.h" #include "RS_Functions.h"
#include "detect_phase_break2.h"
#include "svgen_dq.h" #include "svgen_dq.h"
#include "x_parallel_bus.h" #include "detect_phase_break2.h"
#include "Spartan2E_Functions.h" #include "pid_reg3.h"
#include "xp_rotation_sensor.h"
#include "x_serial_bus.h" #include "x_serial_bus.h"
#include "xp_controller.h" #include "xp_controller.h"
#include "xp_rotation_sensor.h" #include "Spartan2E_Functions.h"
#include "xPeriphSP6_loader.h" #include "xPeriphSP6_loader.h"
#include "log_to_memory.h" #include "x_parallel_bus.h"
#include "global_time.h"
#include "CAN_Setup.h"
#include "log_params.h"
#include "CRC_Functions.h" #include "CRC_Functions.h"
#include "pid_reg3.h" #include "log_params.h"
#include "CAN_Setup.h"
#include "global_time.h"
#include "log_to_memory.h"
#include "IQmathLib.h" #include "IQmathLib.h"
#include "doors_control.h" #include "doors_control.h"
#include "isolation.h" #include "isolation.h"
@ -317,5 +317,5 @@ extern int zero_ADC[20];
int DebugVar_Qnt = 1; int DebugVar_Qnt = 1;
#pragma DATA_SECTION(dbg_vars,".dbgvar_info") #pragma DATA_SECTION(dbg_vars,".dbgvar_info")
DebugVar_t dbg_vars[] = {\ DebugVar_t dbg_vars[] = {\
{(char *)&Bender.KOhms , pt_uint16 , iq_none , "Bender.KOhms" }, \ {(char *)&ADC0startAddr , pt_int64 , iq12 , ADCdr }, \
}; };

BIN
dist/DebugVarEdit.exe vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_bz2.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_ctypes.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_decimal.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_elementtree.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_hashlib.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_lzma.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_socket.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_ssl.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/_wmi.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/libcrypto-3.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/libffi-8.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/libssl-3.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/msvcp140.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/msvcp140_1.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/msvcp140_2.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/pyexpat.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/pyside6.abi3.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/python3.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/python312.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/qt6core.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/qt6gui.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/qt6network.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/qt6pdf.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/qt6svg.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/qt6widgets.dll vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/select.pyd vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/setupVars_GUI.dist/unicodedata.pyd vendored Normal file

Binary file not shown.

BIN
dist/setupVars_GUI.dist/vcruntime140.dll vendored Normal file

Binary file not shown.

Binary file not shown.

5041
nuitka-crash-report.xml Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More