diff --git a/BlenderCtrl_ProdModeInit.xml b/BlenderCtrl_ProdModeInit.xml
new file mode 100644
index 0000000..2485d76
--- /dev/null
+++ b/BlenderCtrl_ProdModeInit.xml
@@ -0,0 +1,477 @@
+
+
+
+
+
+ false
+ TASK2
+
+
+
+
+
+
+
+
+ Standard
+ BlenderCtrl_ProdModeInit
+
+ 2012
+ LAD
+ false
+
+
+
+
+
+
+ it-IT
+
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LAD
+
+
+
+
+
+
+ it-IT
+
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+ it-IT
+ PID Reset Integral
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LAD
+
+
+
+
+
+
+ it-IT
+
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+ it-IT
+ Ctrl Init Errors
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Real
+ 0.0
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ LAD
+
+
+
+
+
+
+ it-IT
+
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+ it-IT
+ RunOut Counter
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
+
+
+
+ it-IT
+ Prode Mode Init
+
+
+
+
+ de-DE
+
+
+
+
+
+ en-US
+
+
+
+
+
+ es-ES
+
+
+
+
+
+ fr-FR
+
+
+
+
+
+ zh-CN
+
+
+
+
+
+ ja-JP
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BlenderCtrl__Main.json b/BlenderCtrl__Main.json
deleted file mode 100644
index 06dd991..0000000
--- a/BlenderCtrl__Main.json
+++ /dev/null
@@ -1,486 +0,0 @@
-{
- "Name": "BlenderCtrl__Main",
- "Number": "2000",
- "ProgrammingLanguage": "LAD",
- "Interface": {},
- "Networks": [
- {
- "index": 1,
- "title": "Clock Generation",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 2,
- "title": "Machine Init",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 3,
- "title": "Filler Head",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 4,
- "title": "Emergency Pressed",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 5,
- "title": "Air and CO2 pressure ok and auxiliary ok",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 6,
- "title": "Blender State Num",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 7,
- "title": "Delay Power On",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 8,
- "title": "Production Mode",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 9,
- "title": "CIp Mode",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 10,
- "title": "Error Faults",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 11,
- "title": "Filler Bottle Count Used to push Product",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 12,
- "title": "Water Bypass Enable",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 13,
- "title": "Still Water Bypass",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 14,
- "title": "Manual Syrup Drain Valve Open - Operator Alarm",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 15,
- "title": "Manual Syrup Drain Valve Open - Operator Alarm",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 16,
- "title": "Maselli Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 17,
- "title": "mPDS Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 18,
- "title": "mPDS Syrup Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 19,
- "title": "Co2 Analog Input",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 20,
- "title": "Quality",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 21,
- "title": "Input Data",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 22,
- "title": "Sel Brix Source Check",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 23,
- "title": "Check Water Cooling System Temperature",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 24,
- "title": "Tank Level",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 25,
- "title": "Production ONS",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 26,
- "title": "Blender Prod Mode Init",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 27,
- "title": "Rinse ONS",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 28,
- "title": "CIP ONS",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 29,
- "title": "CIp Mode Init",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 30,
- "title": "Reset SPWords ",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 31,
- "title": "Blender Run Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 32,
- "title": "Tank Pressure Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 33,
- "title": "Balaiage",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 34,
- "title": "First Production",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 35,
- "title": "CIP MAIN Calling ",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 36,
- "title": "Blender Rinse",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 37,
- "title": "Safeties",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 38,
- "title": "Instrument Scanner",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 39,
- "title": "Vacuum Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 40,
- "title": "Syrup Room Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 41,
- "title": "Blend Procedure Data",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 42,
- "title": "Pneumatic Valve Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 43,
- "title": "Pumps Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 44,
- "title": "Prod Report Manager",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 45,
- "title": "Outputs",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 46,
- "title": "SLIM BLOCK",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 47,
- "title": "Interlocking Panel 1",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 48,
- "title": "Filler Control",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 49,
- "title": "Blender Ctrl Update PWORD",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 50,
- "title": "ResetTotalizer",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 51,
- "title": "ResetWaterTot",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 52,
- "title": "Water VFM Reset Totalizer",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 53,
- "title": "ResetCO2Tot",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 54,
- "title": "Syrup MFM Reset Totalizer",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 55,
- "title": "ResetProductTot",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 56,
- "title": "CO2 MFM Reset Tot",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 57,
- "title": "ResetCO2Tot",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 58,
- "title": "Reset Totalizer",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 59,
- "title": "Reset Totalizer",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 60,
- "title": "Blender Ctrl Command",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 61,
- "title": "DP Global Diag",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 62,
- "title": "Profibus",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 63,
- "title": "Valve Fault",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 64,
- "title": "All Auto",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 65,
- "title": "Ctrl HMI Manual Active",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 66,
- "title": "Mod Copy Recipe",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 67,
- "title": "to HMI - Recipe Management",
- "calls": [],
- "variables": [],
- "logic_elements": []
- },
- {
- "index": 68,
- "title": "Recipe Calculation",
- "calls": [],
- "variables": [],
- "logic_elements": []
- }
- ],
- "all_variables": [],
- "all_function_calls": []
-}
\ No newline at end of file
diff --git a/BlenderCtrl__Main.md b/BlenderCtrl__Main.md
deleted file mode 100644
index c3ae2a0..0000000
--- a/BlenderCtrl__Main.md
+++ /dev/null
@@ -1,171 +0,0 @@
-# Function Block: BlenderCtrl__Main (FC2000)
-
-**Programming Language:** LAD
-
-## Table of Contents
-
-1. [Interface](#interface)
-2. [Summary of Variables](#summary-of-variables)
-3. [Summary of Function Calls](#summary-of-function-calls)
-4. [Networks Detail](#networks-detail)
-5. [Call Tree](#call-tree)
-
-## Interface
-
-_No interface information available_
-
-## Summary of Variables
-
-_No variables found_
-
-
-## Summary of Function Calls
-
-_No function calls found_
-
-
-## Networks Detail (68 networks)
-
-### Network 1: Clock Generation
-
-### Network 2: Machine Init
-
-### Network 3: Filler Head
-
-### Network 4: Emergency Pressed
-
-### Network 5: Air and CO2 pressure ok and auxiliary ok
-
-### Network 6: Blender State Num
-
-### Network 7: Delay Power On
-
-### Network 8: Production Mode
-
-### Network 9: CIp Mode
-
-### Network 10: Error Faults
-
-### Network 11: Filler Bottle Count Used to push Product
-
-### Network 12: Water Bypass Enable
-
-### Network 13: Still Water Bypass
-
-### Network 14: Manual Syrup Drain Valve Open - Operator Alarm
-
-### Network 15: Manual Syrup Drain Valve Open - Operator Alarm
-
-### Network 16: Maselli Control
-
-### Network 17: mPDS Control
-
-### Network 18: mPDS Syrup Control
-
-### Network 19: Co2 Analog Input
-
-### Network 20: Quality
-
-### Network 21: Input Data
-
-### Network 22: Sel Brix Source Check
-
-### Network 23: Check Water Cooling System Temperature
-
-### Network 24: Tank Level
-
-### Network 25: Production ONS
-
-### Network 26: Blender Prod Mode Init
-
-### Network 27: Rinse ONS
-
-### Network 28: CIP ONS
-
-### Network 29: CIp Mode Init
-
-### Network 30: Reset SPWords
-
-### Network 31: Blender Run Control
-
-### Network 32: Tank Pressure Control
-
-### Network 33: Balaiage
-
-### Network 34: First Production
-
-### Network 35: CIP MAIN Calling
-
-### Network 36: Blender Rinse
-
-### Network 37: Safeties
-
-### Network 38: Instrument Scanner
-
-### Network 39: Vacuum Control
-
-### Network 40: Syrup Room Control
-
-### Network 41: Blend Procedure Data
-
-### Network 42: Pneumatic Valve Control
-
-### Network 43: Pumps Control
-
-### Network 44: Prod Report Manager
-
-### Network 45: Outputs
-
-### Network 46: SLIM BLOCK
-
-### Network 47: Interlocking Panel 1
-
-### Network 48: Filler Control
-
-### Network 49: Blender Ctrl Update PWORD
-
-### Network 50: ResetTotalizer
-
-### Network 51: ResetWaterTot
-
-### Network 52: Water VFM Reset Totalizer
-
-### Network 53: ResetCO2Tot
-
-### Network 54: Syrup MFM Reset Totalizer
-
-### Network 55: ResetProductTot
-
-### Network 56: CO2 MFM Reset Tot
-
-### Network 57: ResetCO2Tot
-
-### Network 58: Reset Totalizer
-
-### Network 59: Reset Totalizer
-
-### Network 60: Blender Ctrl Command
-
-### Network 61: DP Global Diag
-
-### Network 62: Profibus
-
-### Network 63: Valve Fault
-
-### Network 64: All Auto
-
-### Network 65: Ctrl HMI Manual Active
-
-### Network 66: Mod Copy Recipe
-
-### Network 67: to HMI - Recipe Management
-
-### Network 68: Recipe Calculation
-
-## Call Tree
-
-```
-Function Call Tree:
-└─ BlenderCtrl__Main
-
-```
diff --git a/LadderToPython.py b/LadderToPython.py
index f95e7b0..ba823b1 100644
--- a/LadderToPython.py
+++ b/LadderToPython.py
@@ -1,639 +1,687 @@
-import xml.etree.ElementTree as ET
-import json
-import os
import sys
+import os
+from lxml import etree
-class SiemensLadderDoc:
- def __init__(self):
- self.function_calls_map = {} # Mapa de función -> llamadas
- self.all_variables = set() # Todas las variables usadas
- self.all_function_calls = set() # Todas las llamadas a funciones
- self.variable_details = {} # Detalles y tipos de variables
+def debug_print(message, enabled=True):
+ """Print debug messages if enabled."""
+ if enabled:
+ print(f"DEBUG: {message}")
- def extract_semantics(self, xml_file):
- """Extrae la semántica principal del archivo XML de Siemens"""
- try:
- tree = ET.parse(xml_file)
- root = tree.getroot()
- except Exception as e:
- return f"Error processing XML: {str(e)}"
- # Extraer información del bloque
- block_info = {}
- block = root.find(".//SW.Blocks.FC") or root.find(".//SW.Blocks.FB")
+def parse_siemens_lad_to_scl(xml_file, debug=True):
+ """
+ Parse a Siemens LAD/FUP XML file and convert to SCL using lxml.
+
+ Args:
+ xml_file: Path to the Siemens XML file
+ debug: Enable debug output
+
+ Returns:
+ String containing the SCL equivalent code
+ """
+ try:
+ # Parse the XML file with lxml
+ parser = etree.XMLParser(remove_blank_text=True)
+ tree = etree.parse(xml_file, parser)
+ root = tree.getroot()
+
+ # Extract namespaces
+ nsmap = root.nsmap
+ debug_print(f"Namespaces: {nsmap}", debug)
+
+ # Create namespace dictionary for XPath queries
+ ns = {}
+ default_ns = nsmap.get(None, "")
+ if default_ns:
+ ns["d"] = default_ns
+
+ # Add other namespaces
+ for prefix, uri in nsmap.items():
+ if prefix is not None:
+ ns[prefix] = uri
+
+ debug_print(f"Namespace dictionary: {ns}", debug)
+
+ # Find the block type (FC, FB, OB)
+ block = None
+ block_type = None
+ for block_tag in ["SW.Blocks.FC", "SW.Blocks.FB", "SW.Blocks.OB"]:
+ # Try with and without namespace
+ block = root.find(f".//{block_tag}")
+ if block is None and "d" in ns:
+ block = root.find(f".//d:{block_tag}", namespaces=ns)
+
+ if block is not None:
+ block_type = block_tag.split(".")[-1]
+ debug_print(f"Found block of type {block_type}", debug)
+ break
if block is None:
- return "No function block found in the file"
+ return "Error: No supported block (FC, FB, OB) found in the XML file."
- # Extraer atributos básicos
- attr_list = block.find("AttributeList")
- if attr_list is not None:
- for attr in attr_list:
- if attr.tag in ["Name", "Number", "ProgrammingLanguage"]:
- block_info[attr.tag] = attr.text
+ # Extract block information
+ block_name = block.find(".//Name").text
+ block_number = block.find(".//Number").text
+ programming_language = block.find(".//ProgrammingLanguage").text
- # Extraer interface (inputs, outputs, temp variables)
- interface_section = block.find(".//Interface")
- if interface_section is not None:
- block_info["Interface"] = self.extract_interface(interface_section)
- # Guardar detalles de los tipos de variables
- for section_name, members in block_info["Interface"].items():
- for member in members:
- var_name = member["Name"]
- var_type = member["Datatype"]
- self.variable_details[var_name] = {
- "type": var_type,
- "section": section_name,
- }
-
- # Procesar todas las redes
- compile_units = block.findall(".//SW.Blocks.CompileUnit")
- networks = []
-
- block_name = block_info.get("Name", "Unknown")
- self.function_calls_map[block_name] = []
-
- for i, unit in enumerate(compile_units):
- network = self.process_network(unit, i + 1, block_name)
- networks.append(network)
-
- # Actualizar todas las variables y llamadas
- for var in network["variables"]:
- self.all_variables.add(var)
-
- for call in network["calls"]:
- self.all_function_calls.add(call)
- if call not in self.function_calls_map[block_name]:
- self.function_calls_map[block_name].append(call)
-
- block_info["Networks"] = networks
-
- # Añadir resúmenes globales
- block_info["all_variables"] = sorted(list(self.all_variables))
- block_info["all_function_calls"] = sorted(list(self.all_function_calls))
-
- return block_info
-
- def extract_interface(self, interface):
- """Extrae la información de interfaz (entradas, salidas, variables temporales)"""
- result = {}
-
- # Buscar secciones directamente
- for section_name in ["Input", "Output", "InOut", "Temp", "Constant", "Return"]:
- section = interface.find(f".//Section[@Name='{section_name}']")
- if section is not None:
- result[section_name] = []
-
- for member in section.findall("./Member"):
- result[section_name].append(
- {"Name": member.get("Name"), "Datatype": member.get("Datatype")}
- )
-
- return result
-
- def process_network(self, network, index, parent_block):
- """Procesa una red para extraer sus elementos principales"""
- result = {
- "index": index,
- "title": self.get_network_title(network),
- "calls": [],
- "variables": [],
- "logic_elements": [],
- "wire_connections": [], # Nueva propiedad para conexiones
- }
-
- # Extraer el XML de la red para analizar la lógica
- network_source = network.find(".//NetworkSource")
- if network_source is not None:
- flg_net = network_source.find(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}FlgNet"
- )
- if flg_net is not None:
- # Extraer partes y conexiones
- parts = flg_net.find(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Parts"
- )
- wires = flg_net.find(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Wires"
- )
-
- # Analizar partes
- if parts is not None:
- # Buscar accesos a variables
- access_elements = parts.findall(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Access"
- )
- for access in access_elements:
- scope = access.get("Scope")
- uid = access.get("UId")
- var_name = self.extract_variable_name(access)
- if var_name and var_name not in result["variables"]:
- result["variables"].append(var_name)
-
- # Buscar llamadas a funciones
- call_elements = parts.findall(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Call"
- )
- for call in call_elements:
- call_info = call.find("CallInfo")
- if call_info is not None:
- call_name = call_info.get("Name", "Unknown")
- if call_name not in result["calls"]:
- result["calls"].append(call_name)
-
- # Registra en el mapa de llamadas para el árbol
- if call_name not in self.function_calls_map:
- self.function_calls_map[call_name] = []
-
- # Buscar elementos lógicos (dentro del elemento Part)
- part_elements = parts.findall(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Part"
- )
- for part in part_elements:
- part_name = part.get("Name")
- if part_name and part_name not in result["logic_elements"]:
- result["logic_elements"].append(part_name)
-
- # Analizar conexiones
- if wires is not None:
- wire_elements = wires.findall(
- ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Wire"
- )
- for wire in wire_elements:
- uid = wire.get("UId")
- # Aquí podríamos guardar más detalles de las conexiones si se necesita
- result["wire_connections"].append(uid)
-
- return result
-
- def get_network_title(self, network):
- """Extrae el título de una red - Versión corregida sin XPath avanzado"""
- # Primer intento: Buscar título en italiano
- title_sections = network.findall(
- ".//MultilingualText[@CompositionName='Title']"
- )
- for title_section in title_sections:
- items = title_section.findall(".//MultilingualTextItem")
- for item in items:
- attr_list = item.find("AttributeList")
- if attr_list is not None:
- culture = attr_list.find("Culture")
- text = attr_list.find("Text")
-
- # Priorizar italiano
- if (
- culture is not None
- and culture.text == "it-IT"
- and text is not None
- and text.text
- ):
- return text.text
-
- # Cualquier otro texto
- if text is not None and text.text:
- return text.text
-
- return "Unnamed Network"
-
- def extract_variable_name(self, access_element):
- """Extrae el nombre de variable desde un elemento Access"""
- symbol = access_element.find("Symbol")
- if symbol is not None:
- components = []
- for component in symbol.findall("Component"):
- components.append(component.get("Name", ""))
- return ".".join(components)
- return None
-
- def generate_call_tree(self):
- """Genera un árbol de llamadas en formato texto"""
- if not self.function_calls_map:
- return "No function calls found."
-
- def build_tree(function_name, depth=0, visited=None):
- if visited is None:
- visited = set()
-
- if function_name in visited:
- return f"{' ' * depth}└─ {function_name} (recursive call)\n"
-
- visited.add(function_name)
- result = f"{' ' * depth}└─ {function_name}\n"
-
- if function_name in self.function_calls_map:
- for called_function in self.function_calls_map[function_name]:
- result += build_tree(called_function, depth + 1, visited.copy())
-
- return result
-
- tree = "Function Call Tree:\n"
- # Comenzar solo con los bloques principales (que no son llamados por otros)
- root_functions = set(self.function_calls_map.keys())
- for func, calls in self.function_calls_map.items():
- for call in calls:
- if call in root_functions:
- root_functions.remove(call)
-
- for main_function in root_functions:
- tree += build_tree(main_function)
- tree += "\n"
-
- return tree
-
- def generate_markdown(self, block_info):
- """Genera documentación en formato markdown"""
- if isinstance(block_info, str):
- return f"# Error\n\n{block_info}"
-
- block_name = block_info.get("Name", "Unknown")
- block_number = block_info.get("Number", "Unknown")
-
- md = f"# Function Block: {block_name} (FC{block_number})\n\n"
- md += f"**Programming Language:** {block_info.get('ProgrammingLanguage', 'Unknown')}\n\n"
-
- # Tabla de Contenido
- md += "## Table of Contents\n\n"
- md += "1. [Interface](#interface)\n"
- md += "2. [Summary of Variables](#summary-of-variables)\n"
- md += "3. [Summary of Function Calls](#summary-of-function-calls)\n"
- md += "4. [Networks Detail](#networks-detail)\n"
- md += "5. [Call Tree](#call-tree)\n\n"
-
- # Información de interfaz
- md += "## Interface\n\n"
- if "Interface" in block_info and block_info["Interface"]:
- for section_name, members in block_info["Interface"].items():
- if members:
- md += f"### {section_name}\n\n"
- md += "| Name | Datatype |\n|------|----------|\n"
-
- for member in members:
- md += f"| {member['Name']} | {member['Datatype']} |\n"
-
- md += "\n"
- else:
- md += "_No interface information available_\n\n"
-
- # Resumen de variables
- md += "## Summary of Variables\n\n"
- if block_info.get("all_variables"):
- md += "All global variables used in this function block:\n\n"
- for var in block_info.get("all_variables"):
- md += f"- `{var}`\n"
- else:
- md += "_No variables found_\n\n"
-
- # Resumen de llamadas a funciones
- md += "\n## Summary of Function Calls\n\n"
- if block_info.get("all_function_calls"):
- md += "All functions called by this function block:\n\n"
- for func in block_info.get("all_function_calls"):
- md += f"- `{func}`\n"
- else:
- md += "_No function calls found_\n\n"
-
- # Detalles de redes
- networks = block_info.get("Networks", [])
- md += f"\n## Networks Detail ({len(networks)} networks)\n\n"
-
- for network in networks:
- md += f"### Network {network['index']}: {network['title']}\n\n"
-
- if network["calls"]:
- md += "**Function Calls:**\n"
- for call in network["calls"]:
- md += f"- `{call}`\n"
- md += "\n"
-
- if network["logic_elements"]:
- md += "**Logic Elements:**\n"
- for element in network["logic_elements"]:
- md += f"- {element}\n"
- md += "\n"
-
- if network["variables"]:
- md += "**Variables Used:**\n"
- for var in network["variables"]:
- md += f"- `{var}`\n"
- md += "\n"
-
- # Árbol de llamadas
- md += "## Call Tree\n\n"
- md += "```\n"
- md += self.generate_call_tree()
- md += "```\n"
-
- return md
-
- def generate_python_class(self, block_info):
- """Genera una representación en clase Python del bloque de función con semántica completa"""
- if isinstance(block_info, str):
- return f"# Error\n\n# {block_info}"
-
- block_name = block_info.get("Name", "Unknown")
- block_number = block_info.get("Number", "Unknown")
-
- py_code = f"class {block_name}:\n"
- py_code += " def __init__(self):\n"
- py_code += (
- f" # Initialize variables for {block_name} (FC{block_number})\n"
+ debug_print(
+ f"Block name: {block_name}, Number: {block_number}, Language: {programming_language}",
+ debug,
)
- # Variables de interfaz
- if "Interface" in block_info:
- interface = block_info["Interface"]
- for section_name, members in interface.items():
- if members:
- py_code += f" # {section_name} variables\n"
- for member in members:
- var_name = member["Name"]
- var_type = member["Datatype"]
- default_value = "None"
- if var_type == "Bool":
- default_value = "False"
- elif var_type in ["Int", "DInt", "Word", "DWord"]:
- default_value = "0"
- elif var_type == "Real":
- default_value = "0.0"
-
- py_code += (
- f" self.{var_name} = {default_value} # {var_type}\n"
- )
- py_code += "\n"
-
- # Variables globales usadas
- if block_info.get("all_variables"):
- py_code += " # Global variables used\n"
- for var in block_info.get("all_variables"):
- if (
- "." in var
- ): # Si es una variable estructurada, solo inicializar la estructura principal
- struct_name = var.split(".")[0]
- if not any(
- v.startswith(f"self.{struct_name} =")
- for v in py_code.split("\n")
- ):
- py_code += f" self.{struct_name} = None\n"
- else:
- if not any(
- v.startswith(f"self.{var} =") for v in py_code.split("\n")
- ):
- # Buscar tipo de variable si existe en detalles
- default_value = "None"
- if var in self.variable_details:
- var_type = self.variable_details[var].get("type")
- if var_type == "Bool":
- default_value = "False"
- elif var_type in ["Int", "DInt", "Word", "DWord"]:
- default_value = "0"
- elif var_type == "Real":
- default_value = "0.0"
-
- py_code += f" self.{var} = {default_value}\n"
- py_code += "\n"
-
- # Agregar variables típicas para los sistemas blender si no se han añadido ya
- common_vars = [
- ("AUX_FALSE", "False"),
- ("HMI_PID", "None"),
- ("Filler_Head_Variables", "None"),
- ("gIN_VoltageOk", "False"),
- ("M19000", "False"),
- ("gEmergencyPressed", "False"),
+ # Start SCL code generation
+ scl_code = [
+ f"// SCL equivalent of {programming_language} block: {block_name} ({block_type} {block_number})"
]
- for var_name, default_value in common_vars:
- if not any(v.startswith(f"self.{var_name} =") for v in py_code.split("\n")):
- py_code += f" self.{var_name} = {default_value}\n"
+ # Extract block title
+ title_element = block.find(".//MultilingualText[@CompositionName='Title']")
+ if title_element is not None:
+ # Try to find title in any available language
+ for text_item in title_element.findall(".//MultilingualTextItem"):
+ culture_elem = text_item.find(".//Culture")
+ text_elem = text_item.find(".//Text")
- # Método run principal
- py_code += " def run(self):\n"
- networks = block_info.get("Networks", [])
-
- if not networks:
- py_code += " pass # No networks found\n"
-
- for network in networks:
- title = (
- network["title"]
- if network["title"] != "Unnamed Network"
- else f"Network {network['index']}"
- )
- py_code += f" # Network {network['index']}: {title}\n"
-
- # Implementar lógica basada en los elementos del network
- if network["calls"]:
- for call in network["calls"]:
- py_code += f" self.{call}()\n"
-
- # Si es un llamado que requerimos implementar específicamente
- if call == "Clock_Signal":
- py_code += (
- " # Generación de señales de reloj para el sistema\n"
- )
- elif call == "BlenderCtrl_MachineInit":
- py_code += " # Inicialización de la máquina mezcladora\n"
-
- # Representar la lógica básica de contactos y bobinas
- element_logic = self._generate_element_logic(network)
- if element_logic:
- py_code += element_logic
-
- # Generar una descripción semántica basada en elementos y variables
- if network["variables"] and not network["calls"]:
- semantics = self._generate_network_semantics(network)
- if semantics:
- py_code += semantics
-
- py_code += "\n"
-
- # Métodos para cada función llamada
- if block_info.get("all_function_calls"):
- for func_call in block_info.get("all_function_calls"):
- py_code += f" def {func_call}(self):\n"
-
- # Implementaciones especiales para funciones conocidas
- if func_call == "Clock_Signal":
- py_code += " # Clock generation implementation\n"
- py_code += " # This generates the system timing signals\n"
- py_code += " self.Clock_10ms = not self.Clock_10ms\n"
- py_code += " \n"
- py_code += " # Generate 100ms and 1s clock signals\n"
- py_code += " if self.Clock_Counter % 10 == 0:\n"
- py_code += " self.Clock_100ms = not self.Clock_100ms\n"
- py_code += " \n"
- py_code += " if self.Clock_Counter % 100 == 0:\n"
- py_code += " self.Clock_1s = not self.Clock_1s\n"
- py_code += " self.Clock_Counter = 0\n"
- py_code += " \n"
- py_code += " self.Clock_Counter += 1\n"
- elif func_call == "BlenderCtrl_MachineInit":
- py_code += " # Initialize blender machine state\n"
- py_code += " if not self.MachineInitialized:\n"
- py_code += " self.MachineState = 0 # IDLE state\n"
- py_code += " self.SystemError = False\n"
- py_code += " self.MachineInitialized = True\n"
- else:
- py_code += f" # Implementation of {func_call}\n"
- py_code += " pass\n"
-
- py_code += "\n"
-
- return py_code
-
- def _generate_element_logic(self, network):
- """Genera código Python para la lógica de los elementos en una red"""
- logic_code = ""
- elements = network["logic_elements"]
- variables = network["variables"]
-
- # Lógica para contactos, bobinas y bloques BLKMOV
- if "Contact" in elements and variables:
- # Si hay contacto y bobina, crear una condición if
- if ("Coil" in elements or "RCoil" in elements) and len(variables) >= 2:
- input_var = variables[0]
- output_var = variables[-1]
-
- logic_code += f" if self.{input_var}:\n"
- if "RCoil" in elements: # Reset Coil - establece en False
- logic_code += (
- f" self.{output_var} = False # Reset coil\n"
+ if text_elem is not None and text_elem.text:
+ culture = (
+ culture_elem.text if culture_elem is not None else "unknown"
)
+ debug_print(f"Found title in {culture}: {text_elem.text}", debug)
+ scl_code.append(f"// Title: {text_elem.text}")
+ break
+
+ # Generate block header
+ if block_type == "FC":
+ scl_code.append(f'FUNCTION "{block_name}" : VOID')
+ elif block_type == "FB":
+ scl_code.append(f'FUNCTION_BLOCK "{block_name}"')
+ elif block_type == "OB":
+ scl_code.append(f'ORGANIZATION_BLOCK "{block_name}"')
+
+ # Add BEGIN marker
+ scl_code.append("BEGIN")
+
+ # Process each network
+ compile_units = block.findall(".//SW.Blocks.CompileUnit")
+ debug_print(f"Found {len(compile_units)} networks", debug)
+
+ for i, unit in enumerate(compile_units):
+ debug_print(f"Processing network {i+1}", debug)
+
+ # Get network title
+ title_element = unit.find(".//MultilingualText[@CompositionName='Title']")
+ network_title = f"Network {i+1}"
+
+ if title_element is not None:
+ for text_item in title_element.findall(".//MultilingualTextItem"):
+ culture_elem = text_item.find(".//Culture")
+ text_elem = text_item.find(".//Text")
+
+ if text_elem is not None and text_elem.text:
+ culture = (
+ culture_elem.text if culture_elem is not None else "unknown"
+ )
+ network_title = f"Network {i+1}: {text_elem.text}"
+ debug_print(
+ f"Network title ({culture}): {network_title}", debug
+ )
+ break
+
+ scl_code.append(f" // {network_title}")
+
+ # Get network source
+ network_source_elem = unit.find(".//NetworkSource")
+ if network_source_elem is None:
+ debug_print(f"No NetworkSource found for network {i+1}", debug)
+ continue
+
+ # Look for FlgNet - the container for LAD/FUP network logic
+ flg_net = None
+
+ # Try direct search
+ flg_net = network_source_elem.find("FlgNet")
+
+ # Try with namespace
+ if flg_net is None and "d" in ns:
+ flg_net = network_source_elem.find("d:FlgNet", namespaces=ns)
+
+ # Try using full XPath with any namespace
+ if flg_net is None:
+ for elem in network_source_elem:
+ if elem.tag.endswith("FlgNet"):
+ flg_net = elem
+ break
+
+ if flg_net is None:
+ debug_print(f"No FlgNet found for network {i+1}", debug)
+ continue
+
+ # Get FlgNet namespace - THIS IS THE KEY FIX
+ flgnet_ns = {}
+ if flg_net.nsmap:
+ debug_print(f"FlgNet has its own namespace: {flg_net.nsmap}", debug)
+ # Add namespace with 'flg' prefix
+ for prefix, uri in flg_net.nsmap.items():
+ if prefix is None:
+ flgnet_ns["flg"] = uri
+ else:
+ flgnet_ns[prefix] = uri
+
+ # Extract Parts and Wires sections - try with proper namespace handling
+ parts = None
+ wires = None
+
+ # Try different methods to find Parts and Wires
+ # Method 1: Direct access
+ parts = flg_net.find("Parts")
+ wires = flg_net.find("Wires")
+
+ # Method 2: With FlgNet namespace
+ if (parts is None or wires is None) and flgnet_ns:
+ parts = parts or flg_net.find("flg:Parts", namespaces=flgnet_ns)
+ wires = wires or flg_net.find("flg:Wires", namespaces=flgnet_ns)
+
+ # Method 3: Try XPath with any namespace
+ if parts is None:
+ for child in flg_net:
+ if child.tag.endswith("Parts"):
+ parts = child
+ break
+
+ if wires is None:
+ for child in flg_net:
+ if child.tag.endswith("Wires"):
+ wires = child
+ break
+
+ if parts is None:
+ debug_print("No Parts element found - check XML namespace", debug)
+ debug_print(
+ f"FlgNet children: {[child.tag for child in flg_net]}", debug
+ )
+ continue
+
+ if wires is None:
+ debug_print("No Wires element found - check XML namespace", debug)
+ continue
+
+ debug_print(f"Successfully found Parts and Wires elements", debug)
+
+ # Process function calls (CALL instructions)
+ call_elements = []
+ # Try both direct and with namespace
+ call_elements.extend(parts.findall("Call"))
+ if flgnet_ns:
+ call_elements.extend(parts.findall("flg:Call", namespaces=flgnet_ns))
+
+ # Try XPath with any namespace
+ if not call_elements:
+ for child in parts:
+ if child.tag.endswith("Call"):
+ call_elements.append(child)
+
+ debug_print(f"Found {len(call_elements)} function calls", debug)
+
+ for call in call_elements:
+ call_uid = call.get("UId")
+ # Try both direct and with namespace for CallInfo
+ call_info = call.find("CallInfo") or (
+ call.find("flg:CallInfo", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace if still not found
+ if call_info is None:
+ for child in call:
+ if child.tag.endswith("CallInfo"):
+ call_info = child
+ break
+
+ if call_info is None:
+ debug_print(
+ f"No CallInfo found for call with UId {call_uid}", debug
+ )
+ continue
+
+ function_name = call_info.get("Name")
+ block_type = call_info.get("BlockType")
+ debug_print(f"Found call to {function_name} ({block_type})", debug)
+
+ # Check if this call is connected to a powerrail (unconditional execution)
+ is_enabled = False
+
+ wire_elements = []
+ wire_elements.extend(wires.findall("Wire"))
+ if flgnet_ns:
+ wire_elements.extend(
+ wires.findall("flg:Wire", namespaces=flgnet_ns)
+ )
+
+ # Try XPath with any namespace
+ if not wire_elements:
+ for child in wires:
+ if child.tag.endswith("Wire"):
+ wire_elements.append(child)
+
+ for wire in wire_elements:
+ # Check if wire has a powerrail source
+ powerrail = wire.find("Powerrail") or (
+ wire.find("flg:Powerrail", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if powerrail is None:
+ for child in wire:
+ if child.tag.endswith("Powerrail"):
+ powerrail = child
+ break
+
+ if powerrail is None:
+ continue
+
+ # Check if wire connects to this call's enable input
+ name_con = wire.find(f"NameCon[@UId='{call_uid}'][@Name='en']") or (
+ wire.find(
+ f"flg:NameCon[@UId='{call_uid}'][@Name='en']",
+ namespaces=flgnet_ns,
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if name_con is None:
+ for child in wire:
+ if (
+ child.tag.endswith("NameCon")
+ and child.get("UId") == call_uid
+ and child.get("Name") == "en"
+ ):
+ name_con = child
+ break
+
+ if name_con is not None:
+ is_enabled = True
+ debug_print(
+ f"Call to {function_name} is directly enabled by powerrail",
+ debug,
+ )
+ break
+
+ if is_enabled:
+ # Generate code for function call
+ scl_code.append(f" {function_name}();")
+
+ # Process MOVE operations
+ move_elements = []
+ move_elements.extend(parts.findall("Part[@Name='Move']"))
+ if flgnet_ns:
+ move_elements.extend(
+ parts.findall("flg:Part[@Name='Move']", namespaces=flgnet_ns)
+ )
+
+ # Try XPath with any namespace
+ if not move_elements:
+ for child in parts:
+ if child.tag.endswith("Part") and child.get("Name") == "Move":
+ move_elements.append(child)
+
+ debug_print(f"Found {len(move_elements)} MOVE operations", debug)
+
+ for move in move_elements:
+ move_uid = move.get("UId")
+ debug_print(f"Processing MOVE with UId {move_uid}", debug)
+
+ # Check if MOVE is directly enabled
+ is_enabled = False
+ for wire in wire_elements:
+ powerrail = wire.find("Powerrail") or (
+ wire.find("flg:Powerrail", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if powerrail is None:
+ for child in wire:
+ if child.tag.endswith("Powerrail"):
+ powerrail = child
+ break
+
+ if powerrail is None:
+ continue
+
+ name_con = wire.find(f"NameCon[@UId='{move_uid}'][@Name='en']") or (
+ wire.find(
+ f"flg:NameCon[@UId='{move_uid}'][@Name='en']",
+ namespaces=flgnet_ns,
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if name_con is None:
+ for child in wire:
+ if (
+ child.tag.endswith("NameCon")
+ and child.get("UId") == move_uid
+ and child.get("Name") == "en"
+ ):
+ name_con = child
+ break
+
+ if name_con is not None:
+ is_enabled = True
+ debug_print("MOVE is directly enabled by powerrail", debug)
+ break
+
+ if not is_enabled:
+ debug_print("MOVE is not directly enabled, skipping", debug)
+ continue
+
+ # Find source value for MOVE
+ source_uid = None
+ for wire in wire_elements:
+ # Find wire that connects to this MOVE's input
+ name_con = wire.find(f"NameCon[@UId='{move_uid}'][@Name='in']") or (
+ wire.find(
+ f"flg:NameCon[@UId='{move_uid}'][@Name='in']",
+ namespaces=flgnet_ns,
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if name_con is None:
+ for child in wire:
+ if (
+ child.tag.endswith("NameCon")
+ and child.get("UId") == move_uid
+ and child.get("Name") == "in"
+ ):
+ name_con = child
+ break
+
+ if name_con is None:
+ continue
+
+ # Get source identifier from wire
+ ident_con = wire.find("IdentCon") or (
+ wire.find("flg:IdentCon", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if ident_con is None:
+ for child in wire:
+ if child.tag.endswith("IdentCon"):
+ ident_con = child
+ break
+
+ if ident_con is not None:
+ source_uid = ident_con.get("UId")
+ debug_print(f"MOVE input connected to UId {source_uid}", debug)
+ break
+
+ # Find source value
+ source_value = None
+ if source_uid:
+ source_access = parts.find(f"Access[@UId='{source_uid}']") or (
+ parts.find(
+ f"flg:Access[@UId='{source_uid}']", namespaces=flgnet_ns
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if source_access is None:
+ for child in parts:
+ if (
+ child.tag.endswith("Access")
+ and child.get("UId") == source_uid
+ ):
+ source_access = child
+ break
+
+ if source_access is not None:
+ scope = source_access.get("Scope")
+ debug_print(f"Source access scope: {scope}", debug)
+
+ if scope == "LiteralConstant":
+ # For literal constants - try with namespace
+ constant = source_access.find(".//Constant") or (
+ source_access.find(
+ ".//flg:Constant", namespaces=flgnet_ns
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if constant is None:
+ for elem in source_access.iter():
+ if elem.tag.endswith("Constant"):
+ constant = elem
+ break
+
+ if constant is not None:
+ # Try with namespace for child elements
+ const_type = constant.find("ConstantType") or (
+ constant.find(
+ "flg:ConstantType", namespaces=flgnet_ns
+ )
+ if flgnet_ns
+ else None
+ )
+ const_value = constant.find("ConstantValue") or (
+ constant.find(
+ "flg:ConstantValue", namespaces=flgnet_ns
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if const_value is None:
+ for child in constant:
+ if child.tag.endswith("ConstantValue"):
+ const_value = child
+ break
+
+ if const_value is not None:
+ source_value = const_value.text
+ debug_print(
+ f"Source value is constant: {source_value}",
+ debug,
+ )
+
+ elif scope == "GlobalVariable":
+ # For variables - try with namespace
+ symbol = source_access.find("Symbol") or (
+ source_access.find("flg:Symbol", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if symbol is None:
+ for child in source_access:
+ if child.tag.endswith("Symbol"):
+ symbol = child
+ break
+
+ if symbol is not None:
+ # Try with namespace for components
+ components = symbol.findall("Component") or (
+ symbol.findall(
+ "flg:Component", namespaces=flgnet_ns
+ )
+ if flgnet_ns
+ else []
+ )
+
+ # Try XPath with any namespace
+ if not components:
+ for child in symbol:
+ if child.tag.endswith("Component"):
+ components.append(child)
+
+ if components:
+ source_value = ".".join(
+ [comp.get("Name") for comp in components]
+ )
+ debug_print(
+ f"Source value is variable: {source_value}",
+ debug,
+ )
+
+ # Find destination for MOVE
+ dest_uid = None
+ for wire in wire_elements:
+ # Find wire that connects from this MOVE's output
+ name_con = wire.find(
+ f"NameCon[@UId='{move_uid}'][@Name='out1']"
+ ) or (
+ wire.find(
+ f"flg:NameCon[@UId='{move_uid}'][@Name='out1']",
+ namespaces=flgnet_ns,
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if name_con is None:
+ for child in wire:
+ if (
+ child.tag.endswith("NameCon")
+ and child.get("UId") == move_uid
+ and child.get("Name") == "out1"
+ ):
+ name_con = child
+ break
+
+ if name_con is None:
+ continue
+
+ # Get destination identifier from wire
+ ident_con = wire.find("IdentCon") or (
+ wire.find("flg:IdentCon", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if ident_con is None:
+ for child in wire:
+ if child.tag.endswith("IdentCon"):
+ ident_con = child
+ break
+
+ if ident_con is not None:
+ dest_uid = ident_con.get("UId")
+ debug_print(f"MOVE output connected to UId {dest_uid}", debug)
+ break
+
+ # Find destination value
+ dest_value = None
+ if dest_uid:
+ dest_access = parts.find(f"Access[@UId='{dest_uid}']") or (
+ parts.find(
+ f"flg:Access[@UId='{dest_uid}']", namespaces=flgnet_ns
+ )
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if dest_access is None:
+ for child in parts:
+ if (
+ child.tag.endswith("Access")
+ and child.get("UId") == dest_uid
+ ):
+ dest_access = child
+ break
+
+ if (
+ dest_access is not None
+ and dest_access.get("Scope") == "GlobalVariable"
+ ):
+ symbol = dest_access.find("Symbol") or (
+ dest_access.find("flg:Symbol", namespaces=flgnet_ns)
+ if flgnet_ns
+ else None
+ )
+
+ # Try XPath with any namespace
+ if symbol is None:
+ for child in dest_access:
+ if child.tag.endswith("Symbol"):
+ symbol = child
+ break
+
+ if symbol is not None:
+ components = symbol.findall("Component") or (
+ symbol.findall("flg:Component", namespaces=flgnet_ns)
+ if flgnet_ns
+ else []
+ )
+
+ # Try XPath with any namespace
+ if not components:
+ for child in symbol:
+ if child.tag.endswith("Component"):
+ components.append(child)
+
+ if components:
+ dest_value = ".".join(
+ [comp.get("Name") for comp in components]
+ )
+ debug_print(
+ f"Destination value is: {dest_value}", debug
+ )
+
+ # Generate SCL for assignment
+ if source_value is not None and dest_value is not None:
+ scl_code.append(f" {dest_value} := {source_value};")
else:
- logic_code += f" self.{output_var} = True\n"
- # Contacto negado (NBox)
- elif "NBox" in elements and len(variables) >= 2:
- input_var = variables[0]
- output_var = variables[-1]
- logic_code += f" if not self.{input_var}:\n"
- logic_code += f" self.{output_var} = True\n"
- # Operación OR
- elif "O" in elements and len(variables) >= 3:
- inputs = variables[:-1] # Todos excepto el último son entradas
- output = variables[-1]
+ debug_print("Could not resolve complete MOVE operation", debug)
- conditions = []
- for var in inputs:
- conditions.append(f"self.{var}")
+ # Close the block
+ if block_type == "FC":
+ scl_code.append("END_FUNCTION;")
+ elif block_type == "FB":
+ scl_code.append("END_FUNCTION_BLOCK;")
+ elif block_type == "OB":
+ scl_code.append("END_ORGANIZATION_BLOCK;")
- logic_code += f" if {' or '.join(conditions)}:\n"
- logic_code += f" self.{output} = True\n"
+ return "\n".join(scl_code)
- # Bloques Move y BLKMOV
- if ("Move" in elements or "BLKMOV" in elements) and len(variables) >= 2:
- source = variables[0]
- target = variables[-1]
+ except Exception as e:
+ import traceback
- # Si el origen tiene un punto, es una estructura
- if "." in source and "." in target:
- src_struct = source.split(".")[0]
- src_field = source.split(".")[1]
- tgt_struct = target.split(".")[0]
- tgt_field = target.split(".")[1]
-
- logic_code += f" # Block move operation\n"
- logic_code += f" if self.{src_struct} is not None and self.{tgt_struct} is not None:\n"
- logic_code += f" self.{target} = self.{source}\n"
- else:
- logic_code += f" # Move operation\n"
- logic_code += f" self.{target} = self.{source}\n"
-
- return logic_code
-
- def _generate_network_semantics(self, network):
- """Genera una descripción semántica para una red basada en sus elementos y variables"""
- elements = network["logic_elements"]
- variables = network["variables"]
- semantics = ""
-
- # Verificar elementos específicos y generar código semántico
- if "Emergency" in " ".join(variables) or "EmergencyPressed" in " ".join(
- variables
- ):
- semantics += " # Control de parada de emergencia\n"
- if len(variables) >= 2:
- in_var = variables[0]
- out_var = variables[-1]
- semantics += f" if self.{in_var} and not self.M19000:\n"
- semantics += f" self.{out_var} = True\n"
-
- elif "Filler_Head" in " ".join(variables) or "FillerHead" in " ".join(
- variables
- ):
- semantics += " # Procesamiento de variables de cabezal de llenado\n"
- semantics += " if self.AUX_FALSE:\n"
- semantics += " # Implementación del BLKMOV para cabezal\n"
- semantics += " self.Filler_Head_Variables.FillerHead = self.HMI_PID.PPM303\n"
- semantics += " self.Block_Move_Err = self.resultado_operacion\n"
-
- elif "Pressure" in " ".join(variables) or "CO2" in " ".join(variables):
- semantics += " # Verificación de presión de CO2 y aire\n"
- semantics += " if self.Air_Pressure_OK and self.CO2_Pressure_OK:\n"
- semantics += " self.System_Pressure_OK = True\n"
-
- elif "Temperature" in " ".join(variables) or "Temp" in " ".join(variables):
- semantics += (
- " # Control de temperatura del sistema de enfriamiento\n"
- )
- semantics += " if self.Temperature_Current > self.Temperature_Max:\n"
- semantics += " self.Temperature_Alarm = True\n"
-
- elif "Tank" in " ".join(variables) or "Level" in " ".join(variables):
- semantics += " # Monitoreo de nivel de tanque\n"
- semantics += " if self.Tank_Level < self.Tank_Level_Min:\n"
- semantics += " self.Tank_Level_Low = True\n"
-
- elif "Reset" in " ".join(variables) and "Totalizer" in " ".join(variables):
- semantics += " # Reseteo de totalizador\n"
- semantics += " if self.Reset_Command:\n"
- semantics += " self.Totalizer_Value = 0\n"
- semantics += " self.Reset_Complete = True\n"
-
- elif "HMI" in " ".join(variables) or "Manual" in " ".join(variables):
- semantics += " # Control de interfaz HMI y modo manual\n"
- semantics += " if self.HMI_Manual_Mode_Requested:\n"
- semantics += " self.System_In_Manual_Mode = True\n"
- semantics += " self.System_In_Auto_Mode = False\n"
-
- return semantics
-
-
-def process_siemens_file(file_path, output_format="markdown"):
- """Procesa un archivo Siemens PLC y genera la documentación"""
- extractor = SiemensLadderDoc()
- block_info = extractor.extract_semantics(file_path)
-
- if output_format.lower() == "json":
- return json.dumps(block_info, indent=2)
- elif output_format.lower() == "markdown":
- return extractor.generate_markdown(block_info)
- elif output_format.lower() == "call_tree":
- extractor.extract_semantics(file_path)
- return extractor.generate_call_tree()
- elif output_format.lower() == "python":
- return extractor.generate_python_class(block_info)
- else:
- return "Unknown output format. Supported formats: markdown, json, call_tree, python"
+ debug_print(f"Exception occurred: {str(e)}", debug)
+ debug_print(traceback.format_exc(), debug)
+ return f"Error converting to SCL: {str(e)}"
+# Main execution
if __name__ == "__main__":
- if len(sys.argv) < 2:
- print("Usage: python script.py [output_format]")
- sys.exit(1)
-
- file_path = sys.argv[1]
- output_format = sys.argv[2] if len(sys.argv) > 2 else "markdown"
-
- result = process_siemens_file(file_path, output_format)
-
- extension = (
- "py"
- if output_format.lower() == "python"
- else "json" if output_format.lower() == "json" else "md"
- )
- output_file = os.path.splitext(file_path)[0] + "." + extension
- with open(output_file, "w", encoding="utf-8") as f:
- f.write(result)
-
- print(f"Documentation generated: {output_file}")
+ if len(sys.argv) > 1:
+ input_file = sys.argv[1]
+ if os.path.exists(input_file):
+ scl = parse_siemens_lad_to_scl(input_file)
+ print(scl)
+ else:
+ print(f"Error: File {input_file} not found")
+ else:
+ print("Usage: python script.py ")
diff --git a/debug_xml.py b/debug_xml.py
deleted file mode 100644
index e69de29..0000000