Добавил скрипт экспорта
This commit is contained in:
@@ -0,0 +1,215 @@
|
|||||||
|
# encoding:utf-8
|
||||||
|
# export_plc_src.py
|
||||||
|
# Экспорт POU / GVL / DUT из открытого проекта CODESYS в текстовые файлы
|
||||||
|
# для хранения в git и работы с Claude Code.
|
||||||
|
#
|
||||||
|
# Запуск:
|
||||||
|
# 1) Внутри CODESYS: Tools -> Scripting -> Execute Script File -> выбрать этот файл
|
||||||
|
# 2) Из командной строки (без открытия GUI):
|
||||||
|
# "C:\Program Files (x86)\CODESYS 3.5.17.30\CODESYS\Common\CODESYS.exe" ^
|
||||||
|
# --Profile="CODESYS V3.5 SP17 Patch 3" --runscript="export_plc_src.py" ^
|
||||||
|
# --project="D:\path\to\ТвойПроект.project"
|
||||||
|
#
|
||||||
|
# Проверено для установки: CODESYS V3.5 SP17 Patch 3 (32-bit)
|
||||||
|
#
|
||||||
|
# По умолчанию папка plc_src создаётся автоматически рядом с файлом .project.
|
||||||
|
# Если авто-определение не сработает, впиши путь вручную в MANUAL_OUTPUT_ROOT ниже.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
|
PRAGMA_RE = re.compile(r"^\s*\{[^}]*\}\s*")
|
||||||
|
|
||||||
|
|
||||||
|
def strip_leading_pragmas(text):
|
||||||
|
"""Убирает ведущие блоки вида {attribute 'qualified_only'} перед проверкой ключевого слова."""
|
||||||
|
while True:
|
||||||
|
new_text = PRAGMA_RE.sub("", text, 1)
|
||||||
|
if new_text == text:
|
||||||
|
return text
|
||||||
|
text = new_text
|
||||||
|
|
||||||
|
|
||||||
|
def get_project_file_path(proj):
|
||||||
|
"""Пытается получить полный путь к .project файлу разными способами,
|
||||||
|
так как имя атрибута может отличаться между версиями CODESYS."""
|
||||||
|
for attr in ("path", "get_path", "project_path", "full_path"):
|
||||||
|
try:
|
||||||
|
val = getattr(proj, attr)
|
||||||
|
val = val() if callable(val) else val
|
||||||
|
if val:
|
||||||
|
return val
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Фолбэк: вытащить путь из текстового представления объекта,
|
||||||
|
# например "Project(Project=0, stPath=C:\...\*.project)"
|
||||||
|
try:
|
||||||
|
s = str(proj)
|
||||||
|
m = re.search(r"stPath=(.*?)\)", s)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def safe_name(obj):
|
||||||
|
try:
|
||||||
|
return obj.get_name(False)
|
||||||
|
except Exception:
|
||||||
|
return "unnamed"
|
||||||
|
|
||||||
|
|
||||||
|
def get_declaration_text(obj):
|
||||||
|
try:
|
||||||
|
if hasattr(obj, "textual_declaration") and obj.textual_declaration is not None:
|
||||||
|
return obj.textual_declaration.text
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_text(obj, declaration_text):
|
||||||
|
"""Собирает декларацию + реализацию (если есть) в единый текст."""
|
||||||
|
text = declaration_text or ""
|
||||||
|
try:
|
||||||
|
if hasattr(obj, "has_textual_implementation") and obj.has_textual_implementation:
|
||||||
|
text += "\r\n(*----- IMPLEMENTATION -----*)\r\n"
|
||||||
|
text += obj.textual_implementation.text
|
||||||
|
except Exception as e:
|
||||||
|
text += "(* ошибка чтения implementation: %s *)\r\n" % e
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def classify(declaration_text):
|
||||||
|
"""Определяет тип объекта по первому ключевому слову декларации."""
|
||||||
|
if not declaration_text:
|
||||||
|
return None
|
||||||
|
stripped = strip_leading_pragmas(declaration_text.strip()).strip().upper()
|
||||||
|
if stripped.startswith(("PROGRAM", "FUNCTION_BLOCK", "FUNCTION", "INTERFACE", "METHOD", "ACTION", "PROPERTY")):
|
||||||
|
return "pou"
|
||||||
|
if stripped.startswith("VAR_GLOBAL"):
|
||||||
|
return "gvl"
|
||||||
|
if stripped.startswith("TYPE"):
|
||||||
|
return "dut"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(folder, name, text):
|
||||||
|
path = os.path.join(folder, name + ".st")
|
||||||
|
with codecs.open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(text)
|
||||||
|
print(" -> %s" % path)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_folder_name(name):
|
||||||
|
"""Убирает символы, недопустимые в имени папки Windows."""
|
||||||
|
invalid = '\\/:*?"<>|'
|
||||||
|
for ch in invalid:
|
||||||
|
name = name.replace(ch, "_")
|
||||||
|
name = name.strip()
|
||||||
|
return name if name else "device"
|
||||||
|
|
||||||
|
|
||||||
|
def make_dirs(base):
|
||||||
|
d = {
|
||||||
|
"pou": os.path.join(base, "POU"),
|
||||||
|
"gvl": os.path.join(base, "GVL"),
|
||||||
|
"dut": os.path.join(base, "DUT"),
|
||||||
|
}
|
||||||
|
for p in d.values():
|
||||||
|
if not os.path.exists(p):
|
||||||
|
os.makedirs(p)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def is_device_node(obj):
|
||||||
|
try:
|
||||||
|
return bool(getattr(obj, "is_device", False))
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def walk(node, dirs, exported):
|
||||||
|
for child in node.get_children(False):
|
||||||
|
name = safe_name(child)
|
||||||
|
decl = get_declaration_text(child)
|
||||||
|
kind = classify(decl)
|
||||||
|
|
||||||
|
if kind == "pou":
|
||||||
|
print("POU: %s" % name)
|
||||||
|
write_file(dirs["pou"], name, get_text(child, decl))
|
||||||
|
exported["pou"] += 1
|
||||||
|
elif kind == "gvl":
|
||||||
|
print("GVL: %s" % name)
|
||||||
|
write_file(dirs["gvl"], name, get_text(child, decl))
|
||||||
|
exported["gvl"] += 1
|
||||||
|
elif kind == "dut":
|
||||||
|
print("DUT: %s" % name)
|
||||||
|
write_file(dirs["dut"], name, get_text(child, decl))
|
||||||
|
exported["dut"] += 1
|
||||||
|
else:
|
||||||
|
exported["skipped"] += 1
|
||||||
|
|
||||||
|
# рекурсивно обходим детей (для FB с методами, папок и т.д.)
|
||||||
|
walk(child, dirs, exported)
|
||||||
|
|
||||||
|
|
||||||
|
# ==== НАСТРОЙКА ====
|
||||||
|
# Оставь пустой строкой "" чтобы папка plc_src создавалась автоматически
|
||||||
|
# рядом с файлом .project. Укажи путь вручную только если авто-определение
|
||||||
|
# не сработает (см. вывод скрипта при запуске).
|
||||||
|
MANUAL_OUTPUT_ROOT = r""
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
proj = projects.primary
|
||||||
|
|
||||||
|
if proj is None:
|
||||||
|
print("ОШИБКА: нет открытого проекта. Открой .project перед запуском скрипта.")
|
||||||
|
else:
|
||||||
|
if MANUAL_OUTPUT_ROOT:
|
||||||
|
OUTPUT_ROOT = MANUAL_OUTPUT_ROOT
|
||||||
|
print("Папка вывода задана вручную: %s" % OUTPUT_ROOT)
|
||||||
|
else:
|
||||||
|
project_file = get_project_file_path(proj)
|
||||||
|
if project_file:
|
||||||
|
project_dir = os.path.dirname(project_file)
|
||||||
|
OUTPUT_ROOT = os.path.join(project_dir, "plc_src")
|
||||||
|
print("Проект найден: %s" % project_file)
|
||||||
|
print("Папка вывода определена автоматически: %s" % OUTPUT_ROOT)
|
||||||
|
else:
|
||||||
|
OUTPUT_ROOT = None
|
||||||
|
print("ОШИБКА: не удалось определить путь к .project автоматически.")
|
||||||
|
print("Задай путь вручную в переменной MANUAL_OUTPUT_ROOT и запусти снова.")
|
||||||
|
|
||||||
|
if OUTPUT_ROOT:
|
||||||
|
exported = {"pou": 0, "gvl": 0, "dut": 0, "skipped": 0}
|
||||||
|
|
||||||
|
print("--- Экспорт в %s ---" % OUTPUT_ROOT)
|
||||||
|
|
||||||
|
top_children = proj.get_children(False)
|
||||||
|
device_nodes = [c for c in top_children if is_device_node(c)]
|
||||||
|
|
||||||
|
if device_nodes:
|
||||||
|
for device in device_nodes:
|
||||||
|
dev_name = sanitize_folder_name(safe_name(device))
|
||||||
|
print("=== Устройство: %s ===" % dev_name)
|
||||||
|
dev_dirs = make_dirs(os.path.join(OUTPUT_ROOT, dev_name))
|
||||||
|
walk(device, dev_dirs, exported)
|
||||||
|
|
||||||
|
# на случай текстовых объектов вне дерева устройств (редко, но на всякий случай)
|
||||||
|
other_dirs = make_dirs(os.path.join(OUTPUT_ROOT, "_common"))
|
||||||
|
for child in top_children:
|
||||||
|
if not is_device_node(child):
|
||||||
|
walk(child, other_dirs, exported)
|
||||||
|
else:
|
||||||
|
# устройств не нашлось (или is_device не сработал) -- работаем как раньше, без разбивки
|
||||||
|
print("Устройства верхнего уровня не найдены, экспорт без разбивки по папкам.")
|
||||||
|
dirs = make_dirs(OUTPUT_ROOT)
|
||||||
|
walk(proj, dirs, exported)
|
||||||
|
|
||||||
|
print("--- Готово: POU=%d, GVL=%d, DUT=%d, пропущено=%d ---" % (
|
||||||
|
exported["pou"], exported["gvl"], exported["dut"], exported["skipped"]
|
||||||
|
))
|
||||||
Reference in New Issue
Block a user