Compare commits

...

3 Commits

19 changed files with 19732 additions and 20008 deletions

View File

@ -1,81 +0,0 @@
{
"block_name": "FC General Lamp",
"block_number": 172,
"language": "LAD",
"block_type": "FC",
"block_comment": "",
"interface": {
"Return": [
{
"name": "Ret_Val",
"datatype": "Void",
"remanence": "NonRetain",
"accessibility": "Public",
"start_value": null,
"comment": null,
"children": [],
"array_elements": {}
}
]
},
"networks": [
{
"id": "4",
"title": "Lamp Alarm - Q.E. - Light Green",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "B",
"title": "Lamp Alarm - Q.E. - Light Red",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "12",
"title": "Lamp Alarm - Q.E. - Buzzer",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "19",
"title": "Lamp Alarm - Q.E. - Light Blue",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "20",
"title": "Lamp - Alarm Presence",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "27",
"title": "Light Signal Phased Stop Machine",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "2E",
"title": "",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
}
],
"source_xml_mod_time": 1749751920.2702959,
"source_xml_size": 39346
}

View File

@ -1,81 +0,0 @@
{
"block_name": "FC General Lamp",
"block_number": 172,
"language": "LAD",
"block_type": "FC",
"block_comment": "",
"interface": {
"Return": [
{
"name": "Ret_Val",
"datatype": "Void",
"remanence": "NonRetain",
"accessibility": "Public",
"start_value": null,
"comment": null,
"children": [],
"array_elements": {}
}
]
},
"networks": [
{
"id": "4",
"title": "Lamp Alarm - Q.E. - Light Green",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "B",
"title": "Lamp Alarm - Q.E. - Light Red",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "12",
"title": "Lamp Alarm - Q.E. - Buzzer",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "19",
"title": "Lamp Alarm - Q.E. - Light Blue",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "20",
"title": "Lamp - Alarm Presence",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "27",
"title": "Light Signal Phased Stop Machine",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
},
{
"id": "2E",
"title": "",
"comment": "",
"language": "LAD",
"logic": [],
"error": "FlgNet not found inside NetworkSource or CompileUnit"
}
],
"source_xml_mod_time": 1749751920.2702959,
"source_xml_size": 39346
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,178 @@
// Block Type: FC
// Block Name (Original): FC TT Devices
// Block Number: 380
// Original Network Languages: SCL, LAD, STL
FUNCTION "FC_TT_Devices" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_TEMP
YLBR1 : Bool;
YLBR2 : Bool;
YLBR3 : Bool;
YLBR4 : Bool;
YLBR5 : Bool;
SystemReady : Bool;
ConveyorsRunning : Bool;
Filler_Run : Bool;
Labeller_Run : Bool;
Line_Empty : Bool;
Reset_Push_Button : Bool;
Line_Empty : Bool;
DUMMY : Bool;
LEVEL_OK : Bool;
Temp : Bool;
Ap_HighLim : Real;
Ap_LowLim : Real;
Apoyo_Rechazo : Bool;
END_VAR
#_0 : Bool; // Auto-generated temporary
#_1500ms : Bool; // Auto-generated temporary
BEGIN
// Network 1: EMD - Body Guide (Original Language: LAD)
"DB TTOP - Body Guide EMD"(Number_Zone := 4, Pressure_Control_Zone_1 := "P1-M37" AND NOT "DB TT Alarm"."A371_04_0", Pressure_Control_Zone_2 := "P1-M39" AND NOT "DB TT Alarm"."A371_04_1", Pressure_Control_Zone_3 := "P1-M41" AND NOT "DB TT Alarm"."A371_04_2", Pressure_Control_Zone_4 := "P1-M42" AND NOT "DB TT Alarm"."A371_04_3", Pressure_Control_Zone_5 := FALSE);
// Network 2: Elevator Door Management - UpStair (Original Language: LAD)
"DB Door Control - Lifter - UpStair"(BUTTON_REQUEST := "E7.2", CLK_FAST := "M1.3", CLK_SLOW := "M1.5", DOOR_OPEN := "E7.6" OR "E7.7" OR NOT "KS165-1C", FBK_MACHINE_OFF := Eq("DB TT Motor 35"."Manage"."STATUS_VFD_ACT_Speed_Hz", 0));
// Network 3: Elevator Door Management - DownStair (Original Language: LAD)
"DB Door Control - Lifter - DownStair"(BUTTON_REQUEST := "E7.0", CLK_FAST := "M1.3", CLK_SLOW := "M1.5", DOOR_OPEN := "E7.4" OR "E7.5" OR NOT "KS165-1C", FBK_MACHINE_OFF := Eq("DB TT Motor 35"."Manage"."STATUS_VFD_ACT_Speed_Hz", 0));
// Network 4: Elevator - Right/ Left Control breaking chains (Original Language: LAD)
"Timer_Chain_Right"(IN := NOT "S1-M35", PT := S5T#1500ms); // TODO: Declarar "Timer_Chain_Right" : TON;
"Timer_Chain_Left"(IN := NOT "S2-M35", PT := S5T#1500ms); // TODO: Declarar "Timer_Chain_Left" : TON;
// Network 5: Energy Saving - Line Empty (Original Language: STL)
// --- BEGIN STL Network 5 ---
```stl
A "B1-M31"
A "B1-M32"
A "B2-M32"
A "B1-M34"
A "B2-M34"
A "B1-M35"
A "B2-M35"
A "B4-M35"
A "B5-M35"
A "B1-M36"
A "B1-M37"
A "B2-M37"
A "B1-M38"
A "B2-M38"
A "B1-M39"
A "B2-M39"
A "B1-M40"
A "B2-M40"
A "B1-M41"
A "B2-M41"
A "B1-M42"
A "B2-M42"
= "Line Empty"
```
// --- END STL Network 5 ---
// Network 6: Energy Saving (Original Language: LAD)
"DB TTOP - Energy Saving"(DownStream_Mac_Req := "M0.1" OR ("DB Signal DownStream Machine - TL25_Q2"."IN_DIG_Signal_01" AND "M0.0"), Sensor_on_the_line := "Line Empty", UpStream_Mac_Empty := "DB TT Run"."TT_Run"."OUT_Cycle_ON" AND (("ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X00" AND "ComSV"."TL25_ReadFromSv"."FromSV"."Data From TL28"."Run") OR (NOT "ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X00" AND NOT "ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X13" AND NOT "ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X14")));
// Network 7: Bottle Counter M153_154 (Original Language: LAD)
"DB Bottle Counter M153_154"(Conveyor_Running := "DB TT Motor 41"."Manage"."STATUS_VFD_Run_FWD" AND "DB TT Motor 42"."Manage"."STATUS_VFD_Run_FWD", PH_Inlet := "Count 1 M42", PH_Outlet := "Count 2 M42", Reset_Button_Allarm := "SH331-1B", Reset_Counter_Henkel := "ComSV"."TL25_WriteToSv"."FromHenkel"."STW"."di0.x");
// Network 8: Bottle Counter M37 (Original Language: LAD)
"DB Bottle Counter_M37"(Conveyor_Running := "DB TT Motor 37"."Manage"."STATUS_VFD_Run_FWD", PH_Inlet := "Count 1 M35", PH_Outlet := "Count 2 M35", Reset_Button_Allarm := "SH331-1B", Reset_Counter_Henkel := "ComSV"."TL25_WriteToSv"."FromHenkel"."STW"."di0.x");
IF "M0.1" THEN
"DB TT Alarm"."A371_03_3" := FALSE;
END_IF;
// Network 9: (Original Language: LAD)
IF "Tag_27" > 5 THEN
"Tag_27" := 0;
END_IF;
"Apoyo_Rechazo" := "B5-M35" OR ("M0.0" AND Eq("Tag_27", 5));
// Network 10: Ejector Bottle (Original Language: STL)
// --- BEGIN STL Network 10 ---
```stl
CALL "DB Ejector Bottle"
```
// --- END STL Network 10 ---
// Network 11: Photocell Teach (Original Language: LAD)
"Delay Photocell Teach"(IN := "DB General"."X203_0", PT := S5T#0.8s); // TODO: Declarar "Delay Photocell Teach" : TP;
"A8.0" := "Delay Photocell Teach".Q;
"DB General"."X203_1" := "Delay Photocell Teach".Q;
IF "Delay Photocell Teach".Q THEN
"DB General"."X203_0" := FALSE;
END_IF;
// Network 12: Elevator - Guide (Original Language: STL)
// --- BEGIN STL Network 12 ---
```stl
CALL "DB Guide Lifter - Lowerator"
```
// --- END STL Network 12 ---
// Network 13: Elevator - Lube (Original Language: STL)
// --- BEGIN STL Network 13 ---
```stl
CALL "DB lube M35"
```
// --- END STL Network 13 ---
// Network 14: Camera Ejector Bottle (Original Language: STL)
// "E38.1"
// --- BEGIN STL Network 14 ---
```stl
CALL "DB Camera Ejector Bottle"
```
// --- END STL Network 14 ---
// Network 15: (Original Language: LAD)
// Network 15 did not produce printable SCL code.
// Network 16: Elevator - SV Reject Y1-M35 (Original Language: LAD)
"Y1-M35" := "Ejector_Tower" OR "Ejector_Camera";
// Network 17: Signal for changeover done (Original Language: LAD)
"Ap_EMD_InPsosition" := Eq("DB TTOP - Body Guide EMD"."Counter_Position", "DB TTOP - Body Guide EMD"."Position_Selection");
"Ap_HighLim" := ("DB Guide Lifter - Lowerator"."Position Selection") + ("DB Guide Lifter - Lowerator"."Range Position");
"Ap_LowLim" := ("DB Guide Lifter - Lowerator"."Position Selection") - ("DB Guide Lifter - Lowerator"."Range Position");
"Ap_LifterGuideInPositon" := "DB Guide Lifter - Lowerator"."Actual Position" >= "Ap_LowLim";
// Network 18: INTERLOCK EMD CHANGEOVER (Original Language: SCL)
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X13 := "ComSV".TL25_ReadFromSv.FromSV.STW.X06;
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X14 := "ComSV".TL25_ReadFromSv.FromSV.STW.X07;
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X15 := "ComSV".TL25_ReadFromSv.FromSV.STW.X08;
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X16 := "ComSV".TL25_ReadFromSv.FromSV.STW.X09;
END_FUNCTION

View File

@ -0,0 +1,178 @@
// Block Type: FC
// Block Name (Original): FC TT Devices
// Block Number: 380
// Original Network Languages: SCL, STL, LAD
FUNCTION "FC_TT_Devices" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_TEMP
YLBR1 : Bool;
YLBR2 : Bool;
YLBR3 : Bool;
YLBR4 : Bool;
YLBR5 : Bool;
SystemReady : Bool;
ConveyorsRunning : Bool;
Filler_Run : Bool;
Labeller_Run : Bool;
Line_Empty : Bool;
Reset_Push_Button : Bool;
Line_Empty : Bool;
DUMMY : Bool;
LEVEL_OK : Bool;
Temp : Bool;
Ap_HighLim : Real;
Ap_LowLim : Real;
Apoyo_Rechazo : Bool;
END_VAR
#_0 : Bool; // Auto-generated temporary
#_1500ms : Bool; // Auto-generated temporary
BEGIN
// Network 1: EMD - Body Guide (Original Language: LAD)
"DB TTOP - Body Guide EMD"(Number_Zone := 4, Pressure_Control_Zone_1 := "P1-M37" AND NOT "DB TT Alarm"."A371_04_0", Pressure_Control_Zone_2 := "P1-M39" AND NOT "DB TT Alarm"."A371_04_1", Pressure_Control_Zone_3 := "P1-M41" AND NOT "DB TT Alarm"."A371_04_2", Pressure_Control_Zone_4 := "P1-M42" AND NOT "DB TT Alarm"."A371_04_3", Pressure_Control_Zone_5 := FALSE);
// Network 2: Elevator Door Management - UpStair (Original Language: LAD)
"DB Door Control - Lifter - UpStair"(BUTTON_REQUEST := "E7.2", CLK_FAST := "M1.3", CLK_SLOW := "M1.5", DOOR_OPEN := "E7.6" OR "E7.7" OR NOT "KS165-1C", FBK_MACHINE_OFF := Eq("DB TT Motor 35"."Manage"."STATUS_VFD_ACT_Speed_Hz", 0));
// Network 3: Elevator Door Management - DownStair (Original Language: LAD)
"DB Door Control - Lifter - DownStair"(BUTTON_REQUEST := "E7.0", CLK_FAST := "M1.3", CLK_SLOW := "M1.5", DOOR_OPEN := "E7.4" OR "E7.5" OR NOT "KS165-1C", FBK_MACHINE_OFF := Eq("DB TT Motor 35"."Manage"."STATUS_VFD_ACT_Speed_Hz", 0));
// Network 4: Elevator - Right/ Left Control breaking chains (Original Language: LAD)
"Timer_Chain_Right"(IN := NOT "S1-M35", PT := S5T#1500ms); // TODO: Declarar "Timer_Chain_Right" : TON;
"Timer_Chain_Left"(IN := NOT "S2-M35", PT := S5T#1500ms); // TODO: Declarar "Timer_Chain_Left" : TON;
// Network 5: Energy Saving - Line Empty (Original Language: STL)
// --- BEGIN STL Network 5 ---
```stl
A "B1-M31"
A "B1-M32"
A "B2-M32"
A "B1-M34"
A "B2-M34"
A "B1-M35"
A "B2-M35"
A "B4-M35"
A "B5-M35"
A "B1-M36"
A "B1-M37"
A "B2-M37"
A "B1-M38"
A "B2-M38"
A "B1-M39"
A "B2-M39"
A "B1-M40"
A "B2-M40"
A "B1-M41"
A "B2-M41"
A "B1-M42"
A "B2-M42"
= "Line Empty"
```
// --- END STL Network 5 ---
// Network 6: Energy Saving (Original Language: LAD)
"DB TTOP - Energy Saving"(DownStream_Mac_Req := "M0.1" OR ("DB Signal DownStream Machine - TL25_Q2"."IN_DIG_Signal_01" AND "M0.0"), Sensor_on_the_line := "Line Empty", UpStream_Mac_Empty := "DB TT Run"."TT_Run"."OUT_Cycle_ON" AND (("ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X00" AND "ComSV"."TL25_ReadFromSv"."FromSV"."Data From TL28"."Run") OR (NOT "ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X00" AND NOT "ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X13" AND NOT "ComSV"."TL25_ReadFromSv"."FromSV"."STW"."X14")));
// Network 7: Bottle Counter M153_154 (Original Language: LAD)
"DB Bottle Counter M153_154"(Conveyor_Running := "DB TT Motor 41"."Manage"."STATUS_VFD_Run_FWD" AND "DB TT Motor 42"."Manage"."STATUS_VFD_Run_FWD", PH_Inlet := "Count 1 M42", PH_Outlet := "Count 2 M42", Reset_Button_Allarm := "SH331-1B", Reset_Counter_Henkel := "ComSV"."TL25_WriteToSv"."FromHenkel"."STW"."di0.x"[1]);
// Network 8: Bottle Counter M37 (Original Language: LAD)
"DB Bottle Counter_M37"(Conveyor_Running := "DB TT Motor 37"."Manage"."STATUS_VFD_Run_FWD", PH_Inlet := "Count 1 M35", PH_Outlet := "Count 2 M35", Reset_Button_Allarm := "SH331-1B", Reset_Counter_Henkel := "ComSV"."TL25_WriteToSv"."FromHenkel"."STW"."di0.x"[1]);
IF "M0.1" THEN
"DB TT Alarm"."A371_03_3" := FALSE;
END_IF;
// Network 9: (Original Language: LAD)
IF "Tag_27" > 5 THEN
"Tag_27" := 0;
END_IF;
"Apoyo_Rechazo" := "B5-M35" OR ("M0.0" AND Eq("Tag_27", 5));
// Network 10: Ejector Bottle (Original Language: STL)
// --- BEGIN STL Network 10 ---
```stl
CALL "DB Ejector Bottle"
```
// --- END STL Network 10 ---
// Network 11: Photocell Teach (Original Language: LAD)
"Delay Photocell Teach"(IN := "DB General"."X203_0", PT := S5T#0.8s); // TODO: Declarar "Delay Photocell Teach" : TP;
"A8.0" := "Delay Photocell Teach".Q;
"DB General"."X203_1" := "Delay Photocell Teach".Q;
IF "Delay Photocell Teach".Q THEN
"DB General"."X203_0" := FALSE;
END_IF;
// Network 12: Elevator - Guide (Original Language: STL)
// --- BEGIN STL Network 12 ---
```stl
CALL "DB Guide Lifter - Lowerator"
```
// --- END STL Network 12 ---
// Network 13: Elevator - Lube (Original Language: STL)
// --- BEGIN STL Network 13 ---
```stl
CALL "DB lube M35"
```
// --- END STL Network 13 ---
// Network 14: Camera Ejector Bottle (Original Language: STL)
// "E38.1"
// --- BEGIN STL Network 14 ---
```stl
CALL "DB Camera Ejector Bottle"
```
// --- END STL Network 14 ---
// Network 15: (Original Language: LAD)
// Network 15 did not produce printable SCL code.
// Network 16: Elevator - SV Reject Y1-M35 (Original Language: LAD)
"Y1-M35" := "Ejector_Tower" OR "Ejector_Camera";
// Network 17: Signal for changeover done (Original Language: LAD)
"Ap_EMD_InPsosition" := Eq("DB TTOP - Body Guide EMD"."Counter_Position", "DB TTOP - Body Guide EMD"."Position_Selection");
"Ap_HighLim" := ("DB Guide Lifter - Lowerator"."Position Selection") + ("DB Guide Lifter - Lowerator"."Range Position");
"Ap_LowLim" := ("DB Guide Lifter - Lowerator"."Position Selection") - ("DB Guide Lifter - Lowerator"."Range Position");
"Ap_LifterGuideInPositon" := "DB Guide Lifter - Lowerator"."Actual Position" >= "Ap_LowLim";
// Network 18: INTERLOCK EMD CHANGEOVER (Original Language: SCL)
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X13 := "ComSV".TL25_ReadFromSv.FromSV.STW.X06;
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X14 := "ComSV".TL25_ReadFromSv.FromSV.STW.X07;
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X15 := "ComSV".TL25_ReadFromSv.FromSV.STW.X08;
"ComSV".TL25_WriteToSv.ToSV.STW.QE1.X16 := "ComSV".TL25_ReadFromSv.FromSV.STW.X09;
END_FUNCTION

View File

@ -1,455 +0,0 @@
// Block Type: FC
// Block Name (Original): FC Ttop Motor M31010
// Block Number: 327
// Original Network Languages: LAD, SCL
FUNCTION "FC_Ttop_Motor_M31010" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INOUT
Motor : STRUCT
RCP_Speed_Fix_01 : Int;
RCP_Speed_Fix_02 : Int;
RCP_Speed_Fix_03 : Int;
RCP_Speed_Fix_04 : Int;
RCP_Speed_Fix_05 : Int;
RCP_Speed_Sync_01 : Int;
RCP_Speed_Sync_02 : Int;
RCP_Speed_Sync_03 : Int;
RCP_Speed_Sync_04 : Int;
RCP_Speed_Sync_05 : Int;
RCP_Timer_01 : Int;
RCP_Timer_02 : Int;
RCP_Timer_03 : Int;
RCP_Timer_04 : Int;
RCP_Timer_05 : Int;
RCP_Speed_Fix_01_mBar : Int;
RCP_Speed_Fix_02_mBar : Int;
RCP_Speed_Fix_03_mBar : Int;
RCP_Speed_Fix_04_mBar : Int;
RCP_Speed_Fix_05_mBar : Int;
RCP_ACC_Ramp : Int;
RCP_DEC_Ramp : Int;
RCP_W044 : Int;
RCP_W046 : Int;
RCP_W048 : Int;
CFG_VFD : Bool;
CFG_DP : Bool;
CFG_Analog_Speed : Bool;
CFG_EN_BWD : Bool;
CFG_Reverse : Bool;
CFG_Motor_N_Sel : Bool;
CFG_PN : Bool;
CFG_X050_7 : Bool;
CFG_TH_CTR_Single : Bool;
CFG_SW_CTR_Single : Bool;
CFG_TRIP_CTR_Single : Bool;
CFG_Speed_User : Bool;
CFG_mBar : Bool;
CFG_SW_CTR_OnOff : Bool;
CFG_Plug_CTR_Single : Bool;
CFG_X051_7 : Bool;
CFG_Min_Speed_Hz : Int;
CFG_Max_Speed_Hz : Int;
CFG_mBar_Type : Byte;
CFG_B57 : Byte;
CFG_Max_mBar : Int;
CFG_EOLO_Zone : Bool;
CFG_TableTop_Zone : Bool;
CFG_Pack_Zone : Bool;
CFG_VIS_Sp_User_Step200 : Bool;
CFG_X060_4 : Bool;
CFG_X060_5 : Bool;
CFG_X060_6 : Bool;
CFG_X060_7 : Bool;
CFG_MPrew : Int;
CFG_MNext : Int;
CFG_DBExternal1 : Int;
CFG_DBExternal2 : Int;
CFG_Vis_Fix_00 : Bool;
CFG_VIS_Fix_01 : Bool;
CFG_VIS_Fix_02 : Bool;
CFG_VIS_Fix_03 : Bool;
CFG_VIS_Fix_04 : Bool;
CFG_VIS_Fix_05 : Bool;
CFG_VIS_Fix_06 : Bool;
CFG_VIS_Fix_07 : Bool;
CFG_VIS_Sync_00 : Bool;
CFG_VIS_Sync_01 : Bool;
CFG_VIS_Sync_02 : Bool;
CFG_VIS_Sync_03 : Bool;
CFG_VIS_Sync_04 : Bool;
CFG_VIS_Sync_05 : Bool;
CFG_VIS_Sync_06 : Bool;
CFG_VIS_Sync_07 : Bool;
CFG_VIS_Timer_00 : Bool;
CFG_VIS_Timer_01 : Bool;
CFG_VIS_Timer_02 : Bool;
CFG_VIS_Timer_03 : Bool;
CFG_VIS_Timer_04 : Bool;
CFG_VIS_Timer_05 : Bool;
CFG_VIS_Timer_06 : Bool;
CFG_VIS_Timer_07 : Bool;
CFG_VIS_SA : Bool;
CFG_VIS_SB : Bool;
CFG_VIS_SC : Bool;
CFG_VIS_SD : Bool;
CFG_VIS_BA : Bool;
CFG_VIS_BB : Bool;
CFG_VIS_EXTA : Bool;
CFG_VIS_EXTB : Bool;
CFG_VIS_SW : Bool;
CFG_VIS_TH : Bool;
CFG_VIS_TRIP : Bool;
CFG_VIS_PAW : Bool;
CFG_VIS_RUN_FWD : Bool;
CFG_VIS_RUN_BWD : Bool;
CFG_VIS_Kspeed : Bool;
CFG_VIS_PLUG : Bool;
CFG_VIS_PB_Auto : Bool;
CFG_VIS_PB_Man : Bool;
CFG_VIS_PB_Jog : Bool;
CFG_VIS_PB_Stop : Bool;
CFG_VIS_PB_Reverse : Bool;
CFG_VIS_PB_sp_05 : Bool;
CFG_VIS_PB_sp_06 : Bool;
CFG_VIS_ManSpeed : Bool;
CFG_VIS_ACT_Torque : Bool;
CFG_VIS_ACC_Ramp : Bool;
CFG_VIS_DEC_Ramp : Bool;
CFG_VIS_X76_3 : Bool;
CFG_VIS_X76_4 : Bool;
CFG_VIS_X76_5 : Bool;
CFG_VIS_X76_6 : Bool;
CFG_VIS_X76 : Bool;
CFG_B77 : Byte;
CFG_W078 : Int;
CFG_Add_Signal_SA : UInt;
CFG_Add_Signal_SB : UInt;
CFG_Add_Signal_SC : UInt;
CFG_Add_Signal_SD : UInt;
CFG_Add_Signal_BA : UInt;
CFG_Add_Signal_BB : UInt;
CFG_Add_Signal_EXTA : UInt;
CFG_Add_Signal_EXTB : UInt;
CFG_Add_Signal_SW : UInt;
CFG_Add_Signal_TH : UInt;
CFG_Add_Signal_TRIP : UInt;
CFG_Add_Signal_PAW : Int;
CFG_Add_Signal_RUN_FWD : UInt;
CFG_Add_Signal_RUN_BWD : UInt;
CFG_Add_Signal_mBar : Int;
CFG_Add_Signal_PLUG : Int;
CFG_Add_Signal_SP02 : Int;
CFG_DB_Machine : Int;
CFG_DB_NextMotor : Int;
CFG_W118 : Int;
CFG_Stop_Empty : Bool;
CFG_Stop_Full : Bool;
CFG_Stop_STBY : Bool;
CFG_Pressurization : Bool;
CFG_EOLO_Press_Speed : Int;
Spare_124 : Array[124..145] of Byte;
CFG_Motor_N : DInt;
CFG_Phylosopy_N : Int;
CFG_Motor_HW_IO : "HW_IO";
CFG_Node_N : Int;
CFG_Inverter_Type : Int;
CFG_W158 : Int;
CFG_Kspeed_User50Hz : Int;
CFG_Min_Speed_User : Int;
CFG_Max_Speed_User : Int;
CFG_W166 : Int;
CFG_W168 : Int;
CFG_EN_mBar_FCT : Bool;
CFG_EN_mBar_FilterALM : Bool;
CFG_Isteresi_mBar : Int;
CFG_Gain_Mbar : Int;
CFG_Max_Speed_FilterALM : Int;
CFG_W178 : Int;
CFG_T_Gain : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
CFG_T_FilterALM : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
IN_PB_Start : Bool;
IN_PB_Stop : Bool;
IN_PB_Reset : Bool;
IN_PB_Silence : Bool;
IN_X200_4 : Bool;
IN_X200_5 : Bool;
IN_X200_6 : Bool;
IN_X200_7 : Bool;
IN_KG_PowerON : Bool;
IN_SW_ManAuto : Bool;
IN_Cycle_ON : Bool;
IN_X201_3 : Bool;
IN_X201_4 : Bool;
IN_X201_5 : Bool;
IN_X201_6 : Bool;
IN_X201_7 : Bool;
IN_SW_HMI_Auto : Bool;
IN_SW_HMI_Man : Bool;
IN_SW_HMI_Jog : Bool;
IN_SW_HMI_Stop : Bool;
IN_SW_HMI_Reverse : Bool;
IN_SW_HMI_sp_05 : Bool;
IN_SW_HMI_sp_06 : Bool;
IN_SW_HMI_ManSpeed : Bool;
IN_SW_HMI_sp_08 : Bool;
IN_SW_HMI_VVFix1 : Bool;
IN_SW_HMI_VVFix2 : Bool;
IN_SW_HMI_VVFix3 : Bool;
IN_SW_HMI_VVFix4 : Bool;
IN_SW_HMI_VVFix5 : Bool;
IN_SW_HMI_sp_14 : Bool;
IN_SW_HMI_sp_15 : Bool;
IN_HMI_ManSpeed : Int;
IN_Signal_SA : Bool;
IN_Signal_SB : Bool;
IN_Signal_SC : Bool;
IN_Signal_SD : Bool;
IN_Signal_BA : Bool;
IN_Signal_BB : Bool;
IN_Signal_EXTA : Bool;
IN_Signal_EXTB : Bool;
IN_Signal_SW : Bool;
IN_Signal_TH : Bool;
IN_Signal_TRIP : Bool;
IN_Signal_RUN_FWD : Bool;
IN_Signal_RUN_BWD : Bool;
IN_Signal_sp_05 : Bool;
IN_Signal_sp_06 : Bool;
IN_Signal_PLUG : Bool;
IN_Signal_sp_08 : Int;
IN_Signal_PEW_mBar : Int;
IN_Signal_mBar : Int;
IN_Motor_DI : "Struct";
IN_W216 : Int;
IN_W218 : Int;
IN_Line_Empty : Bool;
IN_Line_Full : Bool;
IN_Line_StandBy : Bool;
Spare_222 : Array[222..249] of Byte;
OUT_VFD_Run_FWD : Bool;
OUT_VFD_Run_BWD : Bool;
OUT_VFD_Reverse : Bool;
OUT_VFD_Qstop : Bool;
OUT_VFD_Reset : Bool;
OUT_X250_5 : Bool;
OUT_X250_6 : Bool;
OUT_EnergySavingON : Bool;
OUT_VFD_REQ_Speed_Hz : Int;
OUT_VFD_REQ_Speed_User : Int;
OUT_VFD_ACT_Sync_Speed : Int;
OUT_VFD_REQ_Speed_mBar : Int;
OUT_Motor_DO : "Struct";
OUT_W262 : Int;
OUT_W264 : Int;
OUT_W266 : Int;
OUT_W268 : Int;
STATUS_VFD_Run_FWD : Bool;
STATUS_VFD_Run_BWD : Bool;
STATUS_VFD_Trip : Bool;
STATUS_VFD_Warning : Bool;
STATUS_Ready : Bool;
STATUS_VFD_Ready : Bool;
STATUS_VFD_Coasting : Bool;
STATUS_X270_7 : Bool;
STATUS_VFD_ACT_Speed_Hz : Int;
STATUS_VFD_ACT_Speed_Use : Int;
STATUS_VFD_ACT_Torque : Int;
STATUS_MainFault_MovigearADV : Byte;
STATUS_Subfault_MovigearADV : Byte;
STATUS_W280 : Int;
STATUS_W282 : Int;
STATUS_PWR_OFF : Bool;
STATUS_CYCLE_OFF : Bool;
STATUS_ALARM : Bool;
STATUS_AUTO : Bool;
STATUS_MAN : Bool;
STATUS_JOG : Bool;
STATUS_STOP : Bool;
STATUS_X284_7 : Bool;
STATUS_X285_0 : Bool;
STATUS_X285_1 : Bool;
STATUS_X285_2 : Bool;
STATUS_X285_3 : Bool;
STATUS_X285_4 : Bool;
STATUS_X285_5 : Bool;
STATUS_X285_6 : Bool;
STATUS_X285_7 : Bool;
STATUS_NOTRUN : Bool;
STATUS_RUN : Bool;
STATUS_X286_2 : Bool;
STATUS_X286_3 : Bool;
STATUS_X286_4 : Bool;
STATUS_X286_5 : Bool;
STATUS_X286_6 : Bool;
STATUS_X286_7 : Bool;
Spare_288 : Array[288..289] of Byte;
Alarm_09 : Bool;
Alarm_10 : Bool;
Alarm_11 : Bool;
Alarm_12 : Bool;
Alarm_13 : Bool;
Alarm_14 : Bool;
Alarm_15 : Bool;
Alarm_16 : Bool;
Alarm_01 : Bool;
Alarm_02 : Bool;
Alarm_03 : Bool;
Alarm_04 : Bool;
Alarm_05 : Bool;
Alarm_06 : Bool;
Alarm_07 : Bool;
Alarm_08 : Bool;
Spare_292 : Array[292..299] of Byte;
M_Power_ON : Bool;
M_Cycle_ON_AUTO : Bool;
M_Cycle_ON_MAN : Bool;
M_W302 : Int;
M_W304 : Int;
M_W306 : Int;
M_W308 : Int;
M_Delay_Cycle_ON_Auto : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
Spare_320 : Array[320..349] of Byte;
REQ_EN_Run : Bool;
REQ_Start_FWD : Bool;
REQ_Start_BWD : Bool;
REQ_QStop : Bool;
REQ_X350_4 : Bool;
REQ_X350_5 : Bool;
REQ_X350_6 : Bool;
REQ_X350_7 : Bool;
REQ_X351_0 : Bool;
REQ_X351_1 : Bool;
REQ_X351_2 : Bool;
REQ_X351_3 : Bool;
REQ_X351_4 : Bool;
REQ_X351_5 : Bool;
REQ_X351_6 : Bool;
REQ_X351_7 : Bool;
REQ_W352 : Int;
REQ_Master_Speed_Sync : Int;
REQ_W356 : Int;
REQ_Speed_Fix_00_NU : Bool;
REQ_Speed_Fix_01 : Bool;
REQ_Speed_Fix_02 : Bool;
REQ_Speed_Fix_03 : Bool;
REQ_Speed_Fix_04 : Bool;
REQ_Speed_Fix_05 : Bool;
REQ_Speed_Fix_06_NU : Bool;
REQ_Speed_Fix_07_NU : Bool;
REQ_Speed_Sync_00_NU : Bool;
REQ_Speed_Sync_01 : Bool;
REQ_Speed_Sync_02 : Bool;
REQ_Speed_Sync_03 : Bool;
REQ_Speed_Sync_04 : Bool;
REQ_Speed_Sync_05 : Bool;
REQ_Speed_Sync_06_NU : Bool;
REQ_Speed_Sync_07_NU : Bool;
REQ_T01 : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
REQ_T02 : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
REQ_T03 : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
REQ_T04 : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
REQ_T05 : STRUCT
S : Bool;
Q : Bool;
TW : Int;
ST : Int;
ACT : Int;
W008 : Int;
END_STRUCT;
END_STRUCT;
END_VAR
VAR_TEMP
RetVal : Int;
MotorNumber : Int;
DBNumber : Int;
END_VAR
BEGIN
// Network 1: INIT Configuration (Original Language: SCL)
// SCL extraction failed: StructuredText node not found.
// Network 2: (Original Language: LAD)
// Network 2 has no logic elements.
// Network 3: EN run (Original Language: LAD)
"Motor"."REQ_EN_Run" := "M0.1";
// Network 4: REQ Auto RUN (Original Language: LAD)
"Motor"."REQ_Start_FWD" := TRUE;
// Network 5: Request Speed Fix 01 (Original Language: LAD)
"Motor"."REQ_Speed_Fix_01" := "M0.1";
// Network 6: INIT Configuration (Original Language: SCL)
// SCL extraction failed: StructuredText node not found.
END_FUNCTION

View File

@ -6,6 +6,7 @@ import re
# Importar desde las utilidades del parser # Importar desde las utilidades del parser
from .parser_utils import ns, get_multilingual_text from .parser_utils import ns, get_multilingual_text
def reconstruct_scl_from_tokens(st_node): def reconstruct_scl_from_tokens(st_node):
""" """
Reconstruye SCL desde <StructuredText>, mejorando el manejo de Reconstruye SCL desde <StructuredText>, mejorando el manejo de
@ -15,10 +16,21 @@ def reconstruct_scl_from_tokens(st_node):
return "// Error: StructuredText node not found.\n" return "// Error: StructuredText node not found.\n"
scl_parts = [] scl_parts = []
# Usar st:* para obtener todos los elementos hijos dentro del namespace st # Usar st:* para obtener todos los elementos hijos, primero con namespace, luego sin namespace
children = st_node.xpath("./st:*", namespaces=ns) children = st_node.xpath("./st:*", namespaces=ns)
if not children:
# Si no se encuentran con namespace, buscar sin namespace
children = st_node.xpath("./*")
# Set to track elements that have been processed as part of array access
processed_elements = set()
for elem in children: for elem in children:
# Skip elements that have already been processed
elem_id = elem.get("UId")
if elem_id and elem_id in processed_elements:
continue
tag = etree.QName(elem.tag).localname tag = etree.QName(elem.tag).localname
if tag == "Token": if tag == "Token":
@ -40,46 +52,208 @@ def reconstruct_scl_from_tokens(st_node):
scope = elem.get("Scope") scope = elem.get("Scope")
access_str = f"/*_ERR_Scope_{scope}_*/" # Placeholder access_str = f"/*_ERR_Scope_{scope}_*/" # Placeholder
# --- Constantes Locales (estructura diferente) ---
if scope == "LocalConstant":
# Las constantes locales tienen estructura <Constant Name="..." /> directamente
constant_elem = elem.xpath("./st:Constant", namespaces=ns)
if not constant_elem:
constant_elem = elem.xpath("./Constant")
if constant_elem:
const_name = constant_elem[0].get("Name", "_ERR_CONST_NAME_")
access_str = f"#{const_name}" # Las constantes locales van con #
else:
access_str = f"/*_ERR_NO_SYMBOL_IN_{scope}_*/"
# --- Variables --- # --- Variables ---
if scope in [ elif scope in [
"GlobalVariable", "LocalVariable", "TempVariable", "InOutVariable", "GlobalVariable",
"InputVariable", "OutputVariable", "ConstantVariable", "LocalVariable",
"GlobalConstant", "LocalConstant" # Añadir constantes simbólicas "TempVariable",
"InOutVariable",
"InputVariable",
"OutputVariable",
"ConstantVariable",
"GlobalConstant",
]: ]:
# Buscar Symbol tanto con namespace st: como sin namespace
symbol_elem = elem.xpath("./st:Symbol", namespaces=ns) symbol_elem = elem.xpath("./st:Symbol", namespaces=ns)
if not symbol_elem:
symbol_elem = elem.xpath("./Symbol")
if symbol_elem: if symbol_elem:
# Buscar Components tanto con namespace st: como sin namespace
components = symbol_elem[0].xpath("./st:Component", namespaces=ns) components = symbol_elem[0].xpath("./st:Component", namespaces=ns)
if not components:
components = symbol_elem[0].xpath("./Component")
symbol_text_parts = [] symbol_text_parts = []
for i, comp in enumerate(components): for i, comp in enumerate(components):
name = comp.get("Name", "_ERR_COMP_") name = comp.get("Name", "_ERR_COMP_")
if i > 0: symbol_text_parts.append(".") if i > 0:
symbol_text_parts.append(".")
# Check for HasQuotes attribute (adjust namespace if needed) # Check for HasQuotes attribute (adjust namespace if needed)
# El atributo está en el Component o en el Access padre? Probar ambos # El atributo está en el Component o en el Access padre? Probar ambos
has_quotes_comp = comp.get("HasQuotes", "false").lower() == "true" # Check directly on Component has_quotes_comp = (
comp.get("HasQuotes", "false").lower() == "true"
) # Check directly on Component
has_quotes_access = False has_quotes_access = False
access_parent = comp.xpath("ancestor::st:Access[1]", namespaces=ns) # Get immediate Access parent
# Buscar BooleanAttribute tanto con namespace como sin namespace
access_parent = comp.xpath(
"ancestor::st:Access[1]", namespaces=ns
) # Get immediate Access parent with namespace
if not access_parent:
access_parent = comp.xpath(
"ancestor::Access[1]"
) # Get immediate Access parent without namespace
if access_parent: if access_parent:
has_quotes_attr = access_parent[0].xpath("./st:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns) has_quotes_attr = access_parent[0].xpath(
has_quotes_access = has_quotes_attr and has_quotes_attr[0].lower() == 'true' "./st:BooleanAttribute[@Name='HasQuotes']/text()",
namespaces=ns,
)
if not has_quotes_attr:
has_quotes_attr = access_parent[0].xpath(
"./BooleanAttribute[@Name='HasQuotes']/text()"
)
has_quotes_access = (
has_quotes_attr and has_quotes_attr[0].lower() == "true"
)
has_quotes = has_quotes_comp or has_quotes_access has_quotes = has_quotes_comp or has_quotes_access
is_temp = name.startswith("#") is_temp = name.startswith("#")
# Apply quotes based on HasQuotes or if it's the first component and not temp # Apply quotes based on HasQuotes or if it's the first component and not temp
if has_quotes or (i == 0 and not is_temp and '"' not in name): # Avoid double quotes if has_quotes or (
i == 0 and not is_temp and '"' not in name
): # Avoid double quotes
symbol_text_parts.append(f'"{name}"') symbol_text_parts.append(f'"{name}"')
else: else:
symbol_text_parts.append(name) symbol_text_parts.append(name)
# --- Array Index Access --- # --- Array Index Access ---
index_access_nodes = comp.xpath("./st:Access", namespaces=ns) # Verificar si este componente tiene hijos que indican acceso de array
# Buscar estructura: <Token Text="["/> <Access.../> <Token Text="]"/>
children = comp.xpath("./*") # Todos los hijos directos
if len(children) >= 3:
# Verificar patrón: primer hijo es Token "[", último es Token "]"
first_child = children[0]
last_child = children[-1]
first_is_open_bracket = (
etree.QName(first_child.tag).localname == "Token"
and first_child.get("Text") == "["
)
last_is_close_bracket = (
etree.QName(last_child.tag).localname == "Token"
and last_child.get("Text") == "]"
)
if first_is_open_bracket and last_is_close_bracket:
# Hay acceso de array - procesar los elementos entre los corchetes
indices_parts = []
# Mark the bracket tokens and middle elements as processed
first_uid = first_child.get("UId")
last_uid = last_child.get("UId")
if first_uid:
processed_elements.add(first_uid)
if last_uid:
processed_elements.add(last_uid)
for middle_child in children[
1:-1
]: # Todo excepto primer y último hijo
middle_uid = middle_child.get("UId")
if middle_uid:
processed_elements.add(middle_uid)
child_tag = etree.QName(middle_child.tag).localname
if child_tag == "Access":
# Procesar el Access para obtener el índice
scope = middle_child.get("Scope")
if scope == "LiteralConstant":
# Buscar el valor de la constante - tanto con namespace como sin namespace
constant_elem = middle_child.xpath(
"./st:Constant", namespaces=ns
)
if not constant_elem:
constant_elem = middle_child.xpath(
"./Constant"
)
if constant_elem:
# Buscar ConstantValue tanto con namespace como sin namespace
val_nodes = constant_elem[0].xpath(
"./st:ConstantValue", namespaces=ns
)
if not val_nodes:
val_nodes = constant_elem[0].xpath(
"./ConstantValue"
)
if val_nodes and val_nodes[0].text:
indices_parts.append(
val_nodes[0].text.strip()
)
else:
# Para otros tipos de acceso, usar la función recursiva
idx_result = reconstruct_scl_from_tokens(
middle_child
)
if idx_result and idx_result.strip():
indices_parts.append(idx_result.strip())
elif child_tag == "Token":
# Token de separación (como ",")
token_text = middle_child.get("Text", "")
if token_text.strip():
indices_parts.append(token_text)
if indices_parts:
symbol_text_parts.append(
f"[{','.join(indices_parts)}]"
)
else:
# No es acceso de array, buscar Access anidados de la forma tradicional
index_access_nodes = comp.xpath(
"./st:Access", namespaces=ns
)
if not index_access_nodes:
index_access_nodes = comp.xpath("./Access")
if index_access_nodes: if index_access_nodes:
# Llamada recursiva para cada índice indices_text = [
indices_text = [reconstruct_scl_from_tokens(idx_node) for idx_node in index_access_nodes] reconstruct_scl_from_tokens(idx_node)
# Limpiar saltos de línea dentro de los corchetes for idx_node in index_access_nodes
indices_cleaned = [idx.replace('\n', '').strip() for idx in indices_text] ]
symbol_text_parts.append(f"[{','.join(indices_cleaned)}]") indices_cleaned = [
idx.replace("\n", "").strip()
for idx in indices_text
]
symbol_text_parts.append(
f"[{','.join(indices_cleaned)}]"
)
else:
# Menos de 3 hijos, usar búsqueda tradicional de Access
index_access_nodes = comp.xpath(
"./st:Access", namespaces=ns
)
if not index_access_nodes:
index_access_nodes = comp.xpath("./Access")
if index_access_nodes:
indices_text = [
reconstruct_scl_from_tokens(idx_node)
for idx_node in index_access_nodes
]
indices_cleaned = [
idx.replace("\n", "").strip()
for idx in indices_text
]
symbol_text_parts.append(
f"[{','.join(indices_cleaned)}]"
)
access_str = "".join(symbol_text_parts) access_str = "".join(symbol_text_parts)
else: else:
@ -87,41 +261,145 @@ def reconstruct_scl_from_tokens(st_node):
# --- Constantes Literales --- # --- Constantes Literales ---
elif scope == "LiteralConstant": elif scope == "LiteralConstant":
# Buscar nodos Constant tanto con namespace st: como sin namespace
constant_elem = elem.xpath("./st:Constant", namespaces=ns) constant_elem = elem.xpath("./st:Constant", namespaces=ns)
if not constant_elem:
# Si no se encuentran con namespace, buscar sin namespace
constant_elem = elem.xpath("./Constant")
if constant_elem: if constant_elem:
val_elem = constant_elem[0].xpath("./st:ConstantValue/text()", namespaces=ns) # Buscar ConstantValue tanto con namespace como sin namespace
type_elem = constant_elem[0].xpath("./st:ConstantType/text()", namespaces=ns) val_elem = constant_elem[0].xpath(
const_type = type_elem[0].strip().lower() if type_elem and type_elem[0] is not None else "" "./st:ConstantValue/text()", namespaces=ns
const_val = val_elem[0].strip() if val_elem and val_elem[0] is not None else "_ERR_CONSTVAL_" )
if not val_elem:
val_elem = constant_elem[0].xpath("./ConstantValue/text()")
# Si no hay texto directo, buscar el texto del nodo ConstantValue
if not val_elem:
val_nodes = constant_elem[0].xpath("./ConstantValue")
if val_nodes and val_nodes[0].text:
val_elem = [val_nodes[0].text]
# Buscar ConstantType tanto con namespace como sin namespace
type_elem = constant_elem[0].xpath(
"./st:ConstantType/text()", namespaces=ns
)
if not type_elem:
type_elem = constant_elem[0].xpath("./ConstantType/text()")
const_val = (
val_elem[0].strip()
if val_elem and val_elem[0] is not None
else "_ERR_CONSTVAL_"
)
const_type = (
type_elem[0].strip().lower()
if type_elem and type_elem[0] is not None
else ""
)
# Si no hay tipo explícito, inferir de acuerdo al valor
if not const_type:
if const_val.lower() in ["true", "false"]:
const_type = "bool"
elif const_val.startswith("'") and const_val.endswith("'"):
const_type = "string"
elif const_val.isdigit() or (
const_val.startswith("-") and const_val[1:].isdigit()
):
const_type = "int"
elif "." in const_val:
const_type = "real"
else:
const_type = "" # Sin tipo específico, usar valor directo
# Formatear según tipo # Formatear según tipo
if const_type == "bool": access_str = const_val.upper() if const_type == "bool":
access_str = const_val.upper()
elif const_type.lower() == "string": elif const_type.lower() == "string":
if not (const_val.startswith("'") and const_val.endswith("'")):
replaced_val = const_val.replace("'", "''") replaced_val = const_val.replace("'", "''")
access_str = f"'{replaced_val}'" access_str = f"'{replaced_val}'"
else:
access_str = const_val
elif const_type.lower() == "char": elif const_type.lower() == "char":
if not (const_val.startswith("'") and const_val.endswith("'")):
replaced_val = const_val.replace("'", "''") replaced_val = const_val.replace("'", "''")
access_str = f"'{replaced_val}'" access_str = f"'{replaced_val}'"
else:
access_str = const_val
elif const_type == "wstring": elif const_type == "wstring":
replaced_val = const_val.replace("'", "''") replaced_val = const_val.replace("'", "''")
access_str = f"WSTRING#'{replaced_val}'" access_str = f"WSTRING#'{replaced_val}'"
elif const_type == "wchar": elif const_type == "wchar":
replaced_val = const_val.replace("'", "''") replaced_val = const_val.replace("'", "''")
access_str = f"WCHAR#'{replaced_val}'" access_str = f"WCHAR#'{replaced_val}'"
elif const_type == "time": access_str = f"T#{const_val}" elif const_type == "time":
elif const_type == "ltime": access_str = f"LT#{const_val}" access_str = (
elif const_type == "s5time": access_str = f"S5T#{const_val}" f"T#{const_val}"
elif const_type == "date": access_str = f"D#{const_val}" if not const_val.startswith("T#")
elif const_type == "dtl": access_str = f"DTL#{const_val}" else const_val
elif const_type == "dt": access_str = f"DT#{const_val}" )
elif const_type == "tod": access_str = f"TOD#{const_val}" elif const_type == "ltime":
elif const_type in ["int", "dint", "sint", "usint", "uint", "udint", "real", "lreal", "word", "dword", "byte"]: access_str = (
f"LT#{const_val}"
if not const_val.startswith("LT#")
else const_val
)
elif const_type == "s5time":
access_str = (
f"S5T#{const_val}"
if not const_val.startswith("S5T#")
else const_val
)
elif const_type == "date":
access_str = (
f"D#{const_val}"
if not const_val.startswith("D#")
else const_val
)
elif const_type == "dtl":
access_str = (
f"DTL#{const_val}"
if not const_val.startswith("DTL#")
else const_val
)
elif const_type == "dt":
access_str = (
f"DT#{const_val}"
if not const_val.startswith("DT#")
else const_val
)
elif const_type == "tod":
access_str = (
f"TOD#{const_val}"
if not const_val.startswith("TOD#")
else const_val
)
elif const_type in [
"int",
"dint",
"sint",
"usint",
"uint",
"udint",
"real",
"lreal",
"word",
"dword",
"byte",
]:
# Añadir .0 para reales si no tienen decimal # Añadir .0 para reales si no tienen decimal
if const_type in ["real", "lreal"] and '.' not in const_val and 'e' not in const_val.lower(): if (
const_type in ["real", "lreal"]
and "." not in const_val
and "e" not in const_val.lower()
):
access_str = f"{const_val}.0" access_str = f"{const_val}.0"
else: else:
access_str = const_val access_str = const_val
else: # Otros tipos (LWORD, etc.) o desconocidos else: # Otros tipos o sin tipo específico - usar valor directo
access_str = const_val access_str = const_val
else: else:
access_str = "/*_ERR_NOCONST_*/" access_str = "/*_ERR_NOCONST_*/"
@ -140,16 +418,24 @@ def reconstruct_scl_from_tokens(st_node):
for p in params: for p in params:
p_name = p.get("Name", "_ERR_PARAMNAME_") p_name = p.get("Name", "_ERR_PARAMNAME_")
# El valor del parámetro está dentro del nodo Parameter # El valor del parámetro está dentro del nodo Parameter
p_value_node = p.xpath("./st:Access | ./st:Token", namespaces=ns) # Buscar Access o Token p_value_node = p.xpath(
"./st:Access | ./st:Token", namespaces=ns
) # Buscar Access o Token
p_value_scl = "" p_value_scl = ""
if p_value_node: if p_value_node:
p_value_scl = reconstruct_scl_from_tokens(p) # Parsear el contenido del parámetro p_value_scl = reconstruct_scl_from_tokens(
p_value_scl = p_value_scl.replace('\n', '').strip() # Limpiar SCL resultante p
) # Parsear el contenido del parámetro
p_value_scl = p_value_scl.replace(
"\n", ""
).strip() # Limpiar SCL resultante
param_parts.append(f"{p_name} := {p_value_scl}") param_parts.append(f"{p_name} := {p_value_scl}")
# Manejar FB vs FC # Manejar FB vs FC
if call_type == "FB": if call_type == "FB":
instance_node = ci.xpath("./st:Instance/st:Component/@Name", namespaces=ns) instance_node = ci.xpath(
"./st:Instance/st:Component/@Name", namespaces=ns
)
if instance_node: if instance_node:
instance_name = f'"{instance_node[0]}"' instance_name = f'"{instance_node[0]}"'
access_str = f"{instance_name}({', '.join(param_parts)})" access_str = f"{instance_name}({', '.join(param_parts)})"
@ -167,12 +453,28 @@ def reconstruct_scl_from_tokens(st_node):
scl_parts.append(access_str) scl_parts.append(access_str)
elif tag == "Comment" or tag == "LineComment": elif tag == "Comment" or tag == "LineComment":
# Usar get_multilingual_text del parser_utils # Manejar diferentes estructuras de comentarios
comment_text = get_multilingual_text(elem) if tag == "LineComment":
if tag == "Comment": # LineComment tiene estructura <Text> directa, no MultilingualText
text_elem = elem.xpath("./st:Text", namespaces=ns)
if not text_elem:
text_elem = elem.xpath("./Text")
if text_elem and text_elem[0].text:
comment_text = text_elem[0].text.strip()
# Preservar comentarios de bloque multilinea
if "\n" in comment_text:
# Comentario multilinea: usar formato (* ... *)
scl_parts.append(f"(* {comment_text} *)") scl_parts.append(f"(* {comment_text} *)")
else: else:
# Comentario de línea simple
scl_parts.append(f"// {comment_text}") scl_parts.append(f"// {comment_text}")
else:
scl_parts.append("// [Comentario vacío]")
else:
# Comment tradicional: usar get_multilingual_text
comment_text = get_multilingual_text(elem)
scl_parts.append(f"(* {comment_text} *)")
# Ignorar otros tipos de nodos si no son relevantes para el SCL # Ignorar otros tipos de nodos si no son relevantes para el SCL
full_scl = "".join(scl_parts) full_scl = "".join(scl_parts)
@ -181,6 +483,8 @@ def reconstruct_scl_from_tokens(st_node):
output_lines = [] output_lines = []
indent_level = 0 indent_level = 0
indent_str = " " # Dos espacios indent_str = " " # Dos espacios
case_indent_level = 0 # Nivel especial para manejar CASE statements
for line in full_scl.splitlines(): for line in full_scl.splitlines():
trimmed_line = line.strip() trimmed_line = line.strip()
if not trimmed_line: if not trimmed_line:
@ -188,23 +492,58 @@ def reconstruct_scl_from_tokens(st_node):
# output_lines.append("") # output_lines.append("")
continue continue
# Reducir indentación ANTES de imprimir para END, ELSE, etc.
if trimmed_line.upper().startswith(("END_", "UNTIL", "}")) or \
trimmed_line.upper() in ["ELSE", "ELSIF"]:
indent_level = max(0, indent_level - 1)
output_lines.append(indent_str * indent_level + trimmed_line)
# Aumentar indentación DESPUÉS de imprimir para IF, FOR, etc.
# Ser más específico con las palabras clave que aumentan indentación
# Usar .upper() para ignorar mayúsculas/minúsculas
line_upper = trimmed_line.upper() line_upper = trimmed_line.upper()
if line_upper.endswith(("THEN", "DO", "OF", "{")) or \
line_upper.startswith(("IF ", "FOR ", "WHILE ", "CASE ", "REPEAT", "STRUCT")) or \ # Detectar labels de CASE (pattern: #SomeName: o SomeName:)
line_upper == "ELSE": is_case_label = (
":" in trimmed_line
and (
trimmed_line.startswith("#")
or not any(
keyword in line_upper for keyword in ["IF", "ELSIF", "ELSE", "THEN"]
)
)
and line_upper not in ["ELSE:", "ELSIF:"]
and "//" not in trimmed_line.split(":")[0] # Evitar comentarios
)
# Reducir indentación ANTES de imprimir para ciertas palabras clave
if line_upper.startswith(("END_", "UNTIL", "}")):
indent_level = max(0, indent_level - 1)
if line_upper.startswith("END_CASE"):
case_indent_level = 0
elif line_upper in ["ELSE", "ELSIF"] and not is_case_label:
indent_level = max(0, indent_level - 1)
elif is_case_label and case_indent_level > 0:
# Los labels de case van un nivel menos indentados que el contenido del case
indent_level = max(0, case_indent_level)
# Aplicar indentación
current_indent = indent_level
if is_case_label and case_indent_level > 0:
# Los labels de case van un nivel menos que el contenido normal
current_indent = case_indent_level
output_lines.append(indent_str * current_indent + trimmed_line)
# Aumentar indentación DESPUÉS de imprimir para ciertas palabras clave
if line_upper.endswith(("THEN", "DO", "{")) or line_upper == "ELSE":
# Excepción: No indentar después de ELSE IF # Excepción: No indentar después de ELSE IF
if not (line_upper == "ELSE" and "IF" in output_lines[-1].upper()): if not (
line_upper == "ELSE"
and len(output_lines) > 0
and "IF" in output_lines[-1].upper()
):
indent_level += 1 indent_level += 1
elif line_upper.startswith(("IF ", "FOR ", "WHILE ", "REPEAT", "STRUCT")):
indent_level += 1
elif line_upper.startswith("CASE ") and line_upper.endswith(" OF"):
# Manejar CASE especialmente
case_indent_level = indent_level + 1
indent_level += 1
elif is_case_label and case_indent_level > 0:
# Después de un label de case, el contenido va un nivel más indentado
indent_level = case_indent_level + 1
return "\n".join(output_lines) return "\n".join(output_lines)
@ -221,7 +560,9 @@ def parse_scl_network(network_element):
title_elem = network_element.xpath( title_elem = network_element.xpath(
"./ObjectList/MultilingualText[@CompositionName='Title']", namespaces=ns "./ObjectList/MultilingualText[@CompositionName='Title']", namespaces=ns
) )
network_title = get_multilingual_text(title_elem[0]) if title_elem else f"Network {network_id}" network_title = (
get_multilingual_text(title_elem[0]) if title_elem else f"Network {network_id}"
)
comment_elem = network_element.xpath( comment_elem = network_element.xpath(
"./ObjectList/MultilingualText[@CompositionName='Comment']", namespaces=ns "./ObjectList/MultilingualText[@CompositionName='Comment']", namespaces=ns
@ -255,10 +596,11 @@ def parse_scl_network(network_element):
} }
return parsed_network_data return parsed_network_data
# --- Función de Información del Parser --- # --- Función de Información del Parser ---
def get_parser_info(): def get_parser_info():
"""Devuelve la información para este parser.""" """Devuelve la información para este parser."""
return { return {
'language': ['SCL'], # Lista de lenguajes soportados "language": ["SCL"], # Lista de lenguajes soportados
'parser_func': parse_scl_network # Función a llamar "parser_func": parse_scl_network, # Función a llamar
} }

View File

@ -19,20 +19,25 @@ def get_multilingual_text(element, default_lang="en-US-it-IT", fallback_lang=Non
Extrae texto multilingüe de un elemento XML. (v5.2 - DEBUG + XPath ObjectList) Extrae texto multilingüe de un elemento XML. (v5.2 - DEBUG + XPath ObjectList)
""" """
# print(f"--- DEBUG get_multilingual_text v5.2: Iniciando para elemento {element.tag if element is not None else 'None'}, default='{default_lang}' ---") # print(f"--- DEBUG get_multilingual_text v5.2: Iniciando para elemento {element.tag if element is not None else 'None'}, default='{default_lang}' ---")
if element is None: return "" if element is None:
return ""
combined_texts = [] combined_texts = []
languages_to_try = [] languages_to_try = []
# --- Lógica Combinada --- # --- Lógica Combinada ---
is_combined_mode = default_lang and '-' in default_lang and len(default_lang.split('-')) >= 2 is_combined_mode = (
default_lang and "-" in default_lang and len(default_lang.split("-")) >= 2
)
if is_combined_mode: if is_combined_mode:
# print(f"--- DEBUG v5.2: Detectado modo combinado: '{default_lang}' ---") # print(f"--- DEBUG v5.2: Detectado modo combinado: '{default_lang}' ---")
parts = default_lang.split('-') parts = default_lang.split("-")
target_langs = [] target_langs = []
if len(parts) % 2 == 0: if len(parts) % 2 == 0:
for i in range(0, len(parts), 2): target_langs.append(f"{parts[i]}-{parts[i+1]}") for i in range(0, len(parts), 2):
else: target_langs = [] target_langs.append(f"{parts[i]}-{parts[i+1]}")
else:
target_langs = []
if target_langs: if target_langs:
# print(f"--- DEBUG v5.2: Culturas combinadas a buscar: {target_langs} ---") # print(f"--- DEBUG v5.2: Culturas combinadas a buscar: {target_langs} ---")
@ -50,7 +55,8 @@ def get_multilingual_text(element, default_lang="en-US-it-IT", fallback_lang=Non
if text_nodes: if text_nodes:
text_content = text_nodes[0].strip() text_content = text_nodes[0].strip()
# print(f" DEBUG Combinado v5.2: Texto encontrado para '{lang}': '{text_content[:50]}...'") # print(f" DEBUG Combinado v5.2: Texto encontrado para '{lang}': '{text_content[:50]}...'")
if text_content: combined_texts.append(text_content) if text_content:
combined_texts.append(text_content)
# --- FIN CORRECCIÓN XPath v5.2 --- # --- FIN CORRECCIÓN XPath v5.2 ---
if combined_texts: if combined_texts:
# print(f"--- DEBUG v5.2: Modo combinado retornando: '{' - '.join(combined_texts)}' ---") # print(f"--- DEBUG v5.2: Modo combinado retornando: '{' - '.join(combined_texts)}' ---")
@ -59,22 +65,29 @@ def get_multilingual_text(element, default_lang="en-US-it-IT", fallback_lang=Non
# print(f"--- DEBUG v5.2: Modo combinado no encontró textos. Intentando fallback... ---") # print(f"--- DEBUG v5.2: Modo combinado no encontró textos. Intentando fallback... ---")
default_lang = None default_lang = None
except Exception as e_comb: except Exception as e_comb:
print(f" Advertencia: Error procesando modo combinado '{default_lang}': {e_comb}") print(
f" Advertencia: Error procesando modo combinado '{default_lang}': {e_comb}"
)
default_lang = None
else:
default_lang = None default_lang = None
else: default_lang = None
# --- Fin Lógica Combinada --- # --- Fin Lógica Combinada ---
# --- Lógica Normal / Fallback --- # --- Lógica Normal / Fallback ---
# print("--- DEBUG v5.2: Iniciando lógica Normal/Fallback ---") # print("--- DEBUG v5.2: Iniciando lógica Normal/Fallback ---")
if default_lang: languages_to_try.append(default_lang) if default_lang:
if fallback_lang: languages_to_try.append(fallback_lang) languages_to_try.append(default_lang)
if fallback_lang:
languages_to_try.append(fallback_lang)
# print(f" DEBUG v5.2: Idiomas específicos a probar: {languages_to_try}") # print(f" DEBUG v5.2: Idiomas específicos a probar: {languages_to_try}")
try: try:
if languages_to_try: if languages_to_try:
for lang in languages_to_try: for lang in languages_to_try:
# --- CORRECCIÓN XPath v5.2: Añadir ObjectList --- # --- CORRECCIÓN XPath v5.2: Añadir ObjectList ---
xpath_find_item = f"./ObjectList/MultilingualTextItem[AttributeList/Culture='{lang}']" xpath_find_item = (
f"./ObjectList/MultilingualTextItem[AttributeList/Culture='{lang}']"
)
found_items = element.xpath(xpath_find_item, namespaces=ns) found_items = element.xpath(xpath_find_item, namespaces=ns)
# print(f" DEBUG Fallback v5.2: Items encontrados para '{lang}': {len(found_items)}") # print(f" DEBUG Fallback v5.2: Items encontrados para '{lang}': {len(found_items)}")
if found_items: if found_items:
@ -110,10 +123,15 @@ def get_multilingual_text(element, default_lang="en-US-it-IT", fallback_lang=Non
def get_symbol_name(symbol_element): def get_symbol_name(symbol_element):
"""Obtiene el nombre completo de un símbolo desde un elemento <flg:Symbol>.""" """Obtiene el nombre completo de un símbolo desde un elemento <flg:Symbol>, incluyendo índices de arrays."""
if symbol_element is None: if symbol_element is None:
return None return None
try: try:
# Obtener todos los elementos Component
component_elements = symbol_element.xpath("./flg:Component", namespaces=ns)
if not component_elements:
# Fallback al método anterior si no se encuentran elementos Component
components = symbol_element.xpath("./flg:Component/@Name", namespaces=ns) components = symbol_element.xpath("./flg:Component/@Name", namespaces=ns)
return ( return (
".".join( ".".join(
@ -123,6 +141,58 @@ def get_symbol_name(symbol_element):
if components if components
else None else None
) )
symbol_parts = []
for i, comp in enumerate(component_elements):
comp_name = comp.get("Name", "_ERR_COMP_NAME_")
# Añadir separador de punto si no es el primer componente
if i > 0:
symbol_parts.append(".")
# Formatear el nombre del componente
if not comp_name.startswith("#") and '"' not in comp_name:
symbol_parts.append(f'"{comp_name}"')
else:
symbol_parts.append(comp_name)
# Buscar accesos de array dentro del componente
# Buscar tanto con namespace flg: como sin namespace
access_elements = comp.xpath("./flg:Access", namespaces=ns)
if not access_elements:
access_elements = comp.xpath("./Access")
if access_elements:
indices_parts = []
for access_elem in access_elements:
access_scope = access_elem.get("Scope")
if access_scope == "LiteralConstant":
# Buscar el valor de la constante
const_elem = access_elem.xpath("./flg:Constant", namespaces=ns)
if not const_elem:
const_elem = access_elem.xpath("./Constant")
if const_elem:
val_elem = const_elem[0].xpath(
"./flg:ConstantValue", namespaces=ns
)
if not val_elem:
val_elem = const_elem[0].xpath("./ConstantValue")
if val_elem and val_elem[0].text:
indices_parts.append(val_elem[0].text.strip())
else:
# Para otros tipos de acceso, podríamos implementar lógica adicional
# Por ahora, añadir un marcador de error
indices_parts.append(f"/*_ERR_COMPLEX_INDEX_{access_scope}_*/")
if indices_parts:
symbol_parts.append(f"[{','.join(indices_parts)}]")
return "".join(symbol_parts) if symbol_parts else None
except Exception as e: except Exception as e:
print(f"Advertencia: Excepción en get_symbol_name: {e}") print(f"Advertencia: Excepción en get_symbol_name: {e}")
return None return None
@ -142,7 +212,9 @@ def parse_access(access_element):
if address_elem: if address_elem:
addr = address_elem[0] addr = address_elem[0]
# Extraer toda la información disponible sobre la dirección # Extraer toda la información disponible sobre la dirección
info["type"] = "unknown_structure" # Mantener compatible con el código existente info["type"] = (
"unknown_structure" # Mantener compatible con el código existente
)
info["Area"] = addr.get("Area", "DB") info["Area"] = addr.get("Area", "DB")
info["BitOffset"] = addr.get("BitOffset", "0") info["BitOffset"] = addr.get("BitOffset", "0")
info["BlockNumber"] = addr.get("BlockNumber", "") info["BlockNumber"] = addr.get("BlockNumber", "")
@ -262,6 +334,7 @@ def parse_access(access_element):
info["type"] = "error_no_name" info["type"] = "error_no_name"
return info return info
def parse_part(part_element): def parse_part(part_element):
"""Parsea un nodo <flg:Part> de LAD/FBD.""" """Parsea un nodo <flg:Part> de LAD/FBD."""
if part_element is None: if part_element is None:
@ -279,7 +352,9 @@ def parse_part(part_element):
for tv in part_element.xpath("./flg:TemplateValue", namespaces=ns): for tv in part_element.xpath("./flg:TemplateValue", namespaces=ns):
tv_name = tv.get("Name") tv_name = tv.get("Name")
tv_type = tv.get("Type") tv_type = tv.get("Type")
tv_value = tv.text.strip() if tv.text else tv_type # Obtener valor real del elemento tv_value = (
tv.text.strip() if tv.text else tv_type
) # Obtener valor real del elemento
if tv_name: if tv_name:
template_values[tv_name] = tv_value template_values[tv_name] = tv_value
except Exception as e: except Exception as e:
@ -480,8 +555,10 @@ def parse_interface_members(member_elements):
members_data.append(member_info) members_data.append(member_info)
return members_data return members_data
# --- NUEVA FUNCIÓN: Adaptación dinámica de namespaces --- # --- NUEVA FUNCIÓN: Adaptación dinámica de namespaces ---
def adapt_namespaces(root): def adapt_namespaces(root):
"""Actualiza dinámicamente los valores en el diccionario global `ns` para que """Actualiza dinámicamente los valores en el diccionario global `ns` para que
coincidan con los namespaces reales presentes en el XML exportado por TIA. coincidan con los namespaces reales presentes en el XML exportado por TIA.
@ -540,6 +617,7 @@ def adapt_namespaces(root):
# --- función auxiliar privada para adapt_namespaces --- # --- función auxiliar privada para adapt_namespaces ---
def _assign_uri_to_prefix(uri_str: str, out_dict: dict): def _assign_uri_to_prefix(uri_str: str, out_dict: dict):
"""Asigna un URI concreto al prefijo adecuado en `out_dict`.""" """Asigna un URI concreto al prefijo adecuado en `out_dict`."""
if "/Interface/" in uri_str: if "/Interface/" in uri_str:

View File

@ -27,7 +27,7 @@
"display_name": "2: Procesar un archivo individual usando x4", "display_name": "2: Procesar un archivo individual usando x4",
"short_description": "LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL", "short_description": "LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL",
"long_description": "", "long_description": "",
"hidden": false "hidden": true
}, },
"x5_aggregate.py": { "x5_aggregate.py": {
"display_name": "x5_aggregate", "display_name": "x5_aggregate",
@ -40,5 +40,23 @@
"short_description": "3: Limpiar archivos json y md generados por (1)", "short_description": "3: Limpiar archivos json y md generados por (1)",
"long_description": "", "long_description": "",
"hidden": false "hidden": false
},
"debug_array_parsing.py": {
"display_name": "debug_array_parsing",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"test_conversion.py": {
"display_name": "test_conversion",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"x8_manual_gui.py": {
"display_name": "2: Procesar un archivo individual",
"short_description": "x8_manual_gui.py - Interfaz Manual con GUI para XML→SCL",
"long_description": "",
"hidden": false
} }
} }

View File

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Script de prueba para verificar que los índices de arrays se capturen correctamente en LAD/FBD."""
import os
import sys
# Añadir el directorio padre al path para los imports
sys.path.insert(0, os.path.dirname(__file__))
from x1_to_json import convert_xml_to_json
if __name__ == "__main__":
xml_file = ".example/FC TT Devices.xml"
json_file = ".example/FC_TT_Devices_test.json"
print(f"Probando conversión de {xml_file} a {json_file}...")
try:
success = convert_xml_to_json(xml_file, json_file)
if success:
print("Conversión exitosa!")
# Buscar patrones de arrays en el JSON generado
with open(json_file, "r", encoding="utf-8") as f:
content = f.read()
# Buscar di0.x con índices
if '"di0.x"[1]' in content:
print(
"✅ ÉXITO: Se encontró di0.x[1] - los índices de arrays se están capturando correctamente!"
)
elif '"di0.x"[]' in content:
print("❌ PROBLEMA: Se encontró di0.x[] - los índices están vacíos")
elif '"di0.x"' in content:
print(
"❌ PROBLEMA: Se encontró di0.x sin índices - el fix no está funcionando"
)
else:
print("⚠️ No se encontró di0.x en el contenido")
else:
print("Error en la conversión")
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()

View File

@ -1,13 +1,6 @@
{ {
"path": "D:\\Trabajo\\VM\\45 - HENKEL - VM Auto Changeover\\ExportTia", "path": "D:\\Trabajo\\VM\\45 - HENKEL - VM Auto Changeover\\ExportTia",
"history": [ "history": [
"D:\\Trabajo\\VM\\45 - HENKEL - VM Auto Changeover\\ExportTia", "D:\\Trabajo\\VM\\45 - HENKEL - VM Auto Changeover\\ExportTia"
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giorgio in Bosco\\Reporte\\TiaExport",
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\TiaExport",
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia\\Source",
"C:\\Trabajo\\SIDEL\\13 - E5.007560 - Modifica O&U - SAE235\\Reporte\\ExportTia",
"D:\\Trabajo\\VM\\22 - 93841 - Sidel - Tilting\\Reporte\\TiaExports",
"C:\\Trabajo\\SIDEL\\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\\Reporte\\SourceDoc\\SourceXML",
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
] ]
} }

View File

@ -18,6 +18,7 @@ import traceback
import json import json
import datetime # <-- NUEVO: Para timestamps import datetime # <-- NUEVO: Para timestamps
import shutil # <-- ADDED: Import shutil for file copying import shutil # <-- ADDED: Import shutil for file copying
script_root = os.path.dirname( script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
) )
@ -28,8 +29,11 @@ from backend.script_utils import load_configuration
from x1_to_json import convert_xml_to_json from x1_to_json import convert_xml_to_json
from x2_process import process_json_to_scl from x2_process import process_json_to_scl
from x3_generate_scl import generate_scl_or_markdown from x3_generate_scl import generate_scl_or_markdown
# <-- NUEVO: Importar funciones de x4 y x5 --> # <-- NUEVO: Importar funciones de x4 y x5 -->
from x4_cross_reference import generate_cross_references # Asumiendo que x4_cross_reference.py tiene esta función from x4_cross_reference import (
generate_cross_references,
) # Asumiendo que x4_cross_reference.py tiene esta función
from x5_aggregate import aggregate_outputs from x5_aggregate import aggregate_outputs
@ -160,6 +164,7 @@ def check_skip_status(
# --- FUNCIÓN DE LIMPIEZA (x7) --------------------------------------------------------------------------- # --- FUNCIÓN DE LIMPIEZA (x7) ---------------------------------------------------------------------------
def clear_generated_outputs(plc_dir: str = None) -> bool: def clear_generated_outputs(plc_dir: str = None) -> bool:
"""Elimina todos los artefactos (JSON, SCL, MD, logs) generados por este script. """Elimina todos los artefactos (JSON, SCL, MD, logs) generados por este script.
@ -174,25 +179,36 @@ def clear_generated_outputs(plc_dir: str = None) -> bool:
configs = load_configuration() configs = load_configuration()
working_directory = configs.get("working_directory") working_directory = configs.get("working_directory")
if not working_directory or not os.path.isdir(working_directory): if not working_directory or not os.path.isdir(working_directory):
print("Error: 'working_directory' inválido en la configuración.", file=sys.stderr) print(
"Error: 'working_directory' inválido en la configuración.",
file=sys.stderr,
)
return False return False
xml_parser_config = configs.get("level2", {}) xml_parser_config = configs.get("level2", {})
cfg_scl_output_dirname = xml_parser_config.get("scl_output_dir", "scl_output") cfg_scl_output_dirname = xml_parser_config.get("scl_output_dir", "scl_output")
cfg_xref_output_dirname = xml_parser_config.get("xref_output_dir", "xref_output") cfg_xref_output_dirname = xml_parser_config.get(
cfg_aggregated_filename = xml_parser_config.get("aggregated_filename", "full_project_representation.md") "xref_output_dir", "xref_output"
)
cfg_aggregated_filename = xml_parser_config.get(
"aggregated_filename", "full_project_representation.md"
)
# Determinar la lista de PLCs a limpiar # Determinar la lista de PLCs a limpiar
if plc_dir is not None: if plc_dir is not None:
plc_dirs = [os.path.abspath(plc_dir)] plc_dirs = [os.path.abspath(plc_dir)]
if not os.path.isdir(plc_dirs[0]): if not os.path.isdir(plc_dirs[0]):
print(f"Advertencia: El directorio PLC especificado no existe: {plc_dirs[0]}") print(
f"Advertencia: El directorio PLC especificado no existe: {plc_dirs[0]}"
)
return False return False
else: else:
plc_dirs = [] plc_dirs = []
for entry in os.listdir(working_directory): for entry in os.listdir(working_directory):
cand_path = os.path.join(working_directory, entry) cand_path = os.path.join(working_directory, entry)
if os.path.isdir(cand_path) and glob.glob(os.path.join(cand_path, "**", "*.xml"), recursive=True): if os.path.isdir(cand_path) and glob.glob(
os.path.join(cand_path, "**", "*.xml"), recursive=True
):
plc_dirs.append(cand_path) plc_dirs.append(cand_path)
if not plc_dirs: if not plc_dirs:
plc_dirs = [working_directory] plc_dirs = [working_directory]
@ -206,11 +222,15 @@ def clear_generated_outputs(plc_dir: str = None) -> bool:
print(f"\n=== Limpiando PLC: {plc_name_safe} ===") print(f"\n=== Limpiando PLC: {plc_name_safe} ===")
# 1) Eliminar carpetas 'parsing' (y su contenido JSON) # 1) Eliminar carpetas 'parsing' (y su contenido JSON)
for parsing_dir in glob.glob(os.path.join(plc_path, "**", "parsing"), recursive=True): for parsing_dir in glob.glob(
os.path.join(plc_path, "**", "parsing"), recursive=True
):
if os.path.isdir(parsing_dir): if os.path.isdir(parsing_dir):
try: try:
shutil.rmtree(parsing_dir) shutil.rmtree(parsing_dir)
print(f" - Eliminado directorio de parsing: {os.path.relpath(parsing_dir, working_directory)}") print(
f" - Eliminado directorio de parsing: {os.path.relpath(parsing_dir, working_directory)}"
)
total_dirs_removed += 1 total_dirs_removed += 1
except Exception as e: except Exception as e:
print(f" - ERROR al eliminar {parsing_dir}: {e}") print(f" - ERROR al eliminar {parsing_dir}: {e}")
@ -222,7 +242,9 @@ def clear_generated_outputs(plc_dir: str = None) -> bool:
if os.path.isdir(target_dir): if os.path.isdir(target_dir):
try: try:
shutil.rmtree(target_dir) shutil.rmtree(target_dir)
print(f" - Eliminado directorio '{dirname}': {os.path.relpath(target_dir, working_directory)}") print(
f" - Eliminado directorio '{dirname}': {os.path.relpath(target_dir, working_directory)}"
)
total_dirs_removed += 1 total_dirs_removed += 1
except Exception as e: except Exception as e:
print(f" - ERROR al eliminar {target_dir}: {e}") print(f" - ERROR al eliminar {target_dir}: {e}")
@ -233,7 +255,9 @@ def clear_generated_outputs(plc_dir: str = None) -> bool:
if os.path.isfile(agg_file): if os.path.isfile(agg_file):
try: try:
os.remove(agg_file) os.remove(agg_file)
print(f" - Eliminado archivo agregado: {os.path.relpath(agg_file, working_directory)}") print(
f" - Eliminado archivo agregado: {os.path.relpath(agg_file, working_directory)}"
)
total_files_removed += 1 total_files_removed += 1
except Exception as e: except Exception as e:
print(f" - ERROR al eliminar {agg_file}: {e}") print(f" - ERROR al eliminar {agg_file}: {e}")
@ -254,7 +278,11 @@ def clear_generated_outputs(plc_dir: str = None) -> bool:
print("\n--- Resumen de limpieza ---") print("\n--- Resumen de limpieza ---")
print(f" Directorios eliminados: {total_dirs_removed}") print(f" Directorios eliminados: {total_dirs_removed}")
print(f" Archivos eliminados: {total_files_removed}") print(f" Archivos eliminados: {total_files_removed}")
print(" Limpieza completada." if not errors_found else " Limpieza completada con errores.") print(
" Limpieza completada."
if not errors_found
else " Limpieza completada con errores."
)
return not errors_found return not errors_found
except Exception as e: except Exception as e:
@ -262,6 +290,7 @@ def clear_generated_outputs(plc_dir: str = None) -> bool:
traceback.print_exc() traceback.print_exc()
return False return False
# --- FIN FUNCIÓN DE LIMPIEZA ----------------------------------------------------------------------------- # --- FIN FUNCIÓN DE LIMPIEZA -----------------------------------------------------------------------------
@ -270,12 +299,33 @@ if __name__ == "__main__":
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# 1. Analizar argumentos de línea de comandos # 1. Analizar argumentos de línea de comandos
# --plc-dir : ruta al PLC a procesar directamente (modo interno) # --plc-dir : ruta al PLC a procesar directamente (modo interno)
# Si NO se pasa el flag, el script actuará como "orquestador" detectando # --source-xml : archivo XML específico a convertir (modo simple)
# --dest-scl : archivo SCL de destino (modo simple)
# Si NO se pasa ningún flag, el script actuará como "orquestador" detectando
# todos los PLCs bajo el working_directory y lanzándose a sí mismo para # todos los PLCs bajo el working_directory y lanzándose a sí mismo para
# cada uno de ellos. # cada uno de ellos.
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
arg_parser = argparse.ArgumentParser(description="Convertidor XML→SCL (multi-PLC)") arg_parser = argparse.ArgumentParser(
arg_parser.add_argument("--plc-dir", dest="plc_dir", help="Ruta del PLC a procesar (uso interno).", default=None) description="Convertidor XML→SCL (multi-PLC o archivo único)"
)
arg_parser.add_argument(
"--plc-dir",
dest="plc_dir",
help="Ruta del PLC a procesar (uso interno).",
default=None,
)
arg_parser.add_argument(
"--source-xml",
dest="source_xml",
help="Archivo XML específico a convertir (modo simple).",
default=None,
)
arg_parser.add_argument(
"--dest-scl",
dest="dest_scl",
help="Archivo SCL de destino (modo simple).",
default=None,
)
cli_args, _ = arg_parser.parse_known_args() cli_args, _ = arg_parser.parse_known_args()
# Cargar configuración # Cargar configuración
@ -283,13 +333,109 @@ if __name__ == "__main__":
working_directory = configs.get("working_directory") working_directory = configs.get("working_directory")
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# 2. Si NO se indicó --plc-dir ⇒ modo ORQUESTADOR # 2. Si se especificaron --source-xml y --dest-scl ⇒ modo SIMPLE
# Convierte un archivo XML específico a un archivo SCL específico.
# -------------------------------------------------------------------------
if cli_args.source_xml is not None or cli_args.dest_scl is not None:
# Validar que ambos argumentos estén presentes
if cli_args.source_xml is None or cli_args.dest_scl is None:
print(
"Error: Para el modo simple, ambos argumentos --source-xml y --dest-scl son requeridos.",
file=sys.stderr,
)
sys.exit(1)
# Validar que el archivo XML existe
source_xml_path = os.path.abspath(cli_args.source_xml)
if not os.path.isfile(source_xml_path):
print(
f"Error: El archivo XML especificado no existe: {source_xml_path}",
file=sys.stderr,
)
sys.exit(1)
# Validar que el directorio de destino existe
dest_scl_path = os.path.abspath(cli_args.dest_scl)
dest_dir = os.path.dirname(dest_scl_path)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir, exist_ok=True)
print(f"Directorio de destino creado: {dest_dir}")
print(f"=== MODO SIMPLE: Convertir {source_xml_path}{dest_scl_path} ===")
# Crear directorios temporales para el procesamiento
base_filename = os.path.splitext(os.path.basename(source_xml_path))[0]
temp_dir = os.path.join(os.path.dirname(source_xml_path), "parsing")
os.makedirs(temp_dir, exist_ok=True)
json_output_file = os.path.join(temp_dir, f"{base_filename}.json")
processed_json_file = os.path.join(temp_dir, f"{base_filename}_processed.json")
success = False
try:
# Paso 1: XML → JSON
print("Paso 1/3: Convirtiendo XML a JSON...")
success_x1 = convert_xml_to_json(source_xml_path, json_output_file)
if not success_x1:
print("ERROR: Falló la conversión XML → JSON")
sys.exit(1)
# Paso 2: JSON → JSON procesado
print("Paso 2/3: Procesando JSON...")
success_x2 = process_json_to_scl(json_output_file, processed_json_file)
if not success_x2:
print("ERROR: Falló el procesamiento del JSON")
sys.exit(1)
# Paso 3: JSON procesado → SCL/MD
print("Paso 3/3: Generando archivo de salida...")
# Para el modo simple, usamos un directorio temporal como salida
temp_output_dir = os.path.join(
os.path.dirname(dest_scl_path), "temp_scl_output"
)
os.makedirs(temp_output_dir, exist_ok=True)
success_x3 = generate_scl_or_markdown(
processed_json_file, temp_output_dir, os.path.dirname(source_xml_path)
)
if not success_x3:
print("ERROR: Falló la generación del archivo final")
sys.exit(1)
# Encontrar el archivo generado y moverlo al destino final
generated_files = glob.glob(os.path.join(temp_output_dir, "*"))
if not generated_files:
print("ERROR: No se generó ningún archivo de salida")
sys.exit(1)
# Tomar el primer archivo generado (debería ser único en modo simple)
generated_file = generated_files[0]
shutil.move(generated_file, dest_scl_path)
# Limpiar directorios temporales
shutil.rmtree(temp_output_dir, ignore_errors=True)
print(f"✓ Conversión completada exitosamente: {dest_scl_path}")
success = True
except Exception as e:
print(f"ERROR inesperado durante la conversión: {e}", file=sys.stderr)
traceback.print_exc()
sys.exit(1)
sys.exit(0 if success else 1)
# -------------------------------------------------------------------------
# 3. Si NO se indicó --plc-dir ⇒ modo ORQUESTADOR
# Detecta todos los PLC (subdirectorios con al menos un .xml) y lanza # Detecta todos los PLC (subdirectorios con al menos un .xml) y lanza
# este mismo script para cada uno con el flag --plc-dir. # este mismo script para cada uno con el flag --plc-dir.
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
if cli_args.plc_dir is None: if cli_args.plc_dir is None:
if not working_directory or not os.path.isdir(working_directory): if not working_directory or not os.path.isdir(working_directory):
print("Error: 'working_directory' inválido en la configuración.", file=sys.stderr) print(
"Error: 'working_directory' inválido en la configuración.",
file=sys.stderr,
)
sys.exit(1) sys.exit(1)
# Detectar PLCs como subdirectorios que contengan al menos un XML # Detectar PLCs como subdirectorios que contengan al menos un XML
@ -307,50 +453,71 @@ if __name__ == "__main__":
# Ejecutar secuencialmente el script para cada PLC # Ejecutar secuencialmente el script para cada PLC
overall_exit_code = 0 overall_exit_code = 0
for plc_dir in detected_plc_dirs: for plc_dir in detected_plc_dirs:
print(f"\n=== Lanzando procesamiento para PLC: {os.path.basename(plc_dir)} ===") print(
ret = subprocess.call([sys.executable, os.path.abspath(__file__), "--plc-dir", plc_dir]) f"\n=== Lanzando procesamiento para PLC: {os.path.basename(plc_dir)} ==="
)
ret = subprocess.call(
[sys.executable, os.path.abspath(__file__), "--plc-dir", plc_dir]
)
if ret != 0: if ret != 0:
overall_exit_code = 1 # Registrar fallo global si algún PLC falla overall_exit_code = 1 # Registrar fallo global si algún PLC falla
sys.exit(overall_exit_code) sys.exit(overall_exit_code)
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# 3. Modo INTERNO (se recibió --plc-dir) ⇒ procesar sólo ese PLC # 4. Modo INTERNO (se recibió --plc-dir) ⇒ procesar sólo ese PLC
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
xml_project_dir = os.path.abspath(cli_args.plc_dir) xml_project_dir = os.path.abspath(cli_args.plc_dir)
if not os.path.isdir(xml_project_dir): if not os.path.isdir(xml_project_dir):
print(f"Error: El directorio PLC especificado no existe: {xml_project_dir}", file=sys.stderr) print(
f"Error: El directorio PLC especificado no existe: {xml_project_dir}",
file=sys.stderr,
)
sys.exit(1) sys.exit(1)
# Usaremos el nombre del PLC para diferenciar los logs # Usaremos el nombre del PLC para diferenciar los logs
plc_name_safe = os.path.basename(xml_project_dir.strip(os.sep)) plc_name_safe = os.path.basename(xml_project_dir.strip(os.sep))
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
# 3.1 Leer parámetros específicos del grupo para reutilizarlos más abajo # 4.1 Leer parámetros específicos del grupo para reutilizarlos más abajo
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
xml_parser_config = configs.get("level2", {}) xml_parser_config = configs.get("level2", {})
cfg_scl_output_dirname = xml_parser_config.get("scl_output_dir", "scl_output") cfg_scl_output_dirname = xml_parser_config.get("scl_output_dir", "scl_output")
cfg_xref_output_dirname = xml_parser_config.get("xref_output_dir", "xref_output") cfg_xref_output_dirname = xml_parser_config.get("xref_output_dir", "xref_output")
cfg_xref_source_subdir = xml_parser_config.get("xref_source_subdir", "source") cfg_xref_source_subdir = xml_parser_config.get("xref_source_subdir", "source")
cfg_call_xref_filename = xml_parser_config.get("call_xref_filename", "xref_calls_tree.md") cfg_call_xref_filename = xml_parser_config.get(
cfg_db_usage_xref_filename = xml_parser_config.get("db_usage_xref_filename", "xref_db_usage_summary.md") "call_xref_filename", "xref_calls_tree.md"
cfg_plc_tag_xref_filename = xml_parser_config.get("plc_tag_xref_filename", "xref_plc_tags_summary.md") )
cfg_db_usage_xref_filename = xml_parser_config.get(
"db_usage_xref_filename", "xref_db_usage_summary.md"
)
cfg_plc_tag_xref_filename = xml_parser_config.get(
"plc_tag_xref_filename", "xref_plc_tags_summary.md"
)
# Conversión de enteros con control de errores # Conversión de enteros con control de errores
try: try:
cfg_max_call_depth = int(xml_parser_config.get("max_call_depth", 5)) cfg_max_call_depth = int(xml_parser_config.get("max_call_depth", 5))
except (ValueError, TypeError): except (ValueError, TypeError):
print("Advertencia: Valor inválido para 'max_call_depth' en la configuración. Usando valor por defecto 5.", file=sys.stderr) print(
"Advertencia: Valor inválido para 'max_call_depth' en la configuración. Usando valor por defecto 5.",
file=sys.stderr,
)
cfg_max_call_depth = 5 cfg_max_call_depth = 5
try: try:
cfg_max_users_list = int(xml_parser_config.get("max_users_list", 20)) cfg_max_users_list = int(xml_parser_config.get("max_users_list", 20))
except (ValueError, TypeError): except (ValueError, TypeError):
print("Advertencia: Valor inválido para 'max_users_list' en la configuración. Usando valor por defecto 20.", file=sys.stderr) print(
"Advertencia: Valor inválido para 'max_users_list' en la configuración. Usando valor por defecto 20.",
file=sys.stderr,
)
cfg_max_users_list = 20 cfg_max_users_list = 20
cfg_aggregated_filename = xml_parser_config.get("aggregated_filename", "full_project_representation.md") cfg_aggregated_filename = xml_parser_config.get(
"aggregated_filename", "full_project_representation.md"
)
# Generar un nombre de log específico por PLC # Generar un nombre de log específico por PLC
log_filename_dynamic = f"log_{plc_name_safe}.txt" log_filename_dynamic = f"log_{plc_name_safe}.txt"
@ -390,37 +557,59 @@ if __name__ == "__main__":
) )
xml_files_found.sort() xml_files_found.sort()
[ [
log_message(f" - {os.path.relpath(xml_file, working_directory)}", log_f) # Mostrar ruta relativa al working_directory original log_message(
f" - {os.path.relpath(xml_file, working_directory)}", log_f
) # Mostrar ruta relativa al working_directory original
for xml_file in xml_files_found for xml_file in xml_files_found
] ]
# --- NUEVO: Identificar bloques SCL nativos --- # --- NUEVO: Identificar bloques SCL nativos ---
log_message("\n--- Fase 0.5: Identificando archivos .scl nativos existentes ---", log_f) log_message(
"\n--- Fase 0.5: Identificando archivos .scl nativos existentes ---", log_f
)
native_scl_blocks = set() native_scl_blocks = set()
try: try:
# Usar un patrón similar a la Fase 1.5 para encontrar SCLs en el proyecto fuente # Usar un patrón similar a la Fase 1.5 para encontrar SCLs en el proyecto fuente
search_scl_pattern_native = os.path.join(xml_project_dir, "**", "*.scl") search_scl_pattern_native = os.path.join(xml_project_dir, "**", "*.scl")
existing_scl_files_native = glob.glob(search_scl_pattern_native, recursive=True) existing_scl_files_native = glob.glob(
search_scl_pattern_native, recursive=True
)
# Excluir directorios de salida para evitar auto-referencias si están anidados # Excluir directorios de salida para evitar auto-referencias si están anidados
scl_output_dir_abs_native = os.path.abspath(os.path.join(xml_project_dir, cfg_scl_output_dirname)) scl_output_dir_abs_native = os.path.abspath(
xref_output_dir_abs_native = os.path.abspath(os.path.join(xml_project_dir, cfg_xref_output_dirname)) os.path.join(xml_project_dir, cfg_scl_output_dirname)
)
xref_output_dir_abs_native = os.path.abspath(
os.path.join(xml_project_dir, cfg_xref_output_dirname)
)
for scl_file_path in existing_scl_files_native: for scl_file_path in existing_scl_files_native:
if not os.path.abspath(os.path.dirname(scl_file_path)).startswith(scl_output_dir_abs_native) and \ if not os.path.abspath(os.path.dirname(scl_file_path)).startswith(
not os.path.abspath(os.path.dirname(scl_file_path)).startswith(xref_output_dir_abs_native): scl_output_dir_abs_native
) and not os.path.abspath(os.path.dirname(scl_file_path)).startswith(
xref_output_dir_abs_native
):
base_name = os.path.splitext(os.path.basename(scl_file_path))[0] base_name = os.path.splitext(os.path.basename(scl_file_path))[0]
native_scl_blocks.add(base_name) native_scl_blocks.add(base_name)
log_message(f"Se identificaron {len(native_scl_blocks)} posibles bloques SCL nativos (con archivo .scl).", log_f) log_message(
f"Se identificaron {len(native_scl_blocks)} posibles bloques SCL nativos (con archivo .scl).",
log_f,
)
except Exception as e: except Exception as e:
log_message(f"Error durante la identificación de SCL nativos: {e}. Se continuará sin priorización.", log_f) log_message(
f"Error durante la identificación de SCL nativos: {e}. Se continuará sin priorización.",
log_f,
)
# --- FIN NUEVO --- # --- FIN NUEVO ---
# --- Directorios de salida --- # --- Directorios de salida ---
# Estos directorios ahora se crearán DENTRO de xml_project_dir (es decir, dentro de 'PLC') # Estos directorios ahora se crearán DENTRO de xml_project_dir (es decir, dentro de 'PLC')
scl_output_dir = os.path.join(xml_project_dir, cfg_scl_output_dirname) # Usar valor de config scl_output_dir = os.path.join(
xref_output_dir = os.path.join(xml_project_dir, cfg_xref_output_dirname) # Usar valor de config xml_project_dir, cfg_scl_output_dirname
) # Usar valor de config
xref_output_dir = os.path.join(
xml_project_dir, cfg_xref_output_dirname
) # Usar valor de config
# <-- ADDED: Ensure output directories exist --> # <-- ADDED: Ensure output directories exist -->
os.makedirs(scl_output_dir, exist_ok=True) os.makedirs(scl_output_dir, exist_ok=True)
os.makedirs(xref_output_dir, exist_ok=True) os.makedirs(xref_output_dir, exist_ok=True)
@ -448,7 +637,8 @@ if __name__ == "__main__":
os.makedirs(parsing_dir, exist_ok=True) os.makedirs(parsing_dir, exist_ok=True)
json_output_file = os.path.join(parsing_dir, f"{base_filename}.json") json_output_file = os.path.join(parsing_dir, f"{base_filename}.json")
processed_json_filepath = os.path.join( processed_json_filepath = os.path.join(
parsing_dir, f"{base_filename}_processed.json" # <-- Corregido: nombre correcto parsing_dir,
f"{base_filename}_processed.json", # <-- Corregido: nombre correcto
) )
# --- NUEVO: Comprobar si es un SCL nativo --- # --- NUEVO: Comprobar si es un SCL nativo ---
@ -489,11 +679,16 @@ if __name__ == "__main__":
success_x1 = True # Asumir éxito si se salta success_x1 = True # Asumir éxito si se salta
else: else:
log_message( log_message(
f"--- Ejecutando x1 (convert_xml_to_json) para: {relative_path} ---", log_f f"--- Ejecutando x1 (convert_xml_to_json) para: {relative_path} ---",
log_f,
) )
success_x1 = convert_xml_to_json(xml_filepath, json_output_file) success_x1 = convert_xml_to_json(xml_filepath, json_output_file)
if not success_x1: if not success_x1:
log_message(f"--- x1 FALLÓ para: {relative_path} ---", log_f, also_print=False) # La función ya imprime el error log_message(
f"--- x1 FALLÓ para: {relative_path} ---",
log_f,
also_print=False,
) # La función ya imprime el error
if not success_x1: if not success_x1:
failed_count += 1 failed_count += 1
@ -507,11 +702,18 @@ if __name__ == "__main__":
success_x2 = True # Asumir éxito si se salta success_x2 = True # Asumir éxito si se salta
else: else:
log_message( log_message(
f"--- Ejecutando x2 (process_json_to_scl) para: {relative_path} ---", log_f f"--- Ejecutando x2 (process_json_to_scl) para: {relative_path} ---",
log_f,
)
success_x2 = process_json_to_scl(
json_output_file, processed_json_filepath
) )
success_x2 = process_json_to_scl(json_output_file, processed_json_filepath)
if not success_x2: if not success_x2:
log_message(f"--- x2 FALLÓ para: {relative_path} ---", log_f, also_print=False) log_message(
f"--- x2 FALLÓ para: {relative_path} ---",
log_f,
also_print=False,
)
if not success_x2: if not success_x2:
failed_count += 1 failed_count += 1
@ -523,7 +725,8 @@ if __name__ == "__main__":
skipped_partial_count += 1 # Se saltó x1/x2 pero se ejecuta x3 skipped_partial_count += 1 # Se saltó x1/x2 pero se ejecuta x3
log_message( log_message(
f"--- Ejecutando x3 (generate_scl_or_markdown) para: {relative_path} ---", log_f f"--- Ejecutando x3 (generate_scl_or_markdown) para: {relative_path} ---",
log_f,
) )
# Asegurar que el directorio de salida final exista ANTES de llamar a la función # Asegurar que el directorio de salida final exista ANTES de llamar a la función
os.makedirs(scl_output_dir, exist_ok=True) os.makedirs(scl_output_dir, exist_ok=True)
@ -531,7 +734,11 @@ if __name__ == "__main__":
processed_json_filepath, scl_output_dir, xml_project_dir processed_json_filepath, scl_output_dir, xml_project_dir
) )
if not success_x3: if not success_x3:
log_message(f"--- x3 FALLÓ para: {relative_path} ---", log_f, also_print=False) log_message(
f"--- x3 FALLÓ para: {relative_path} ---",
log_f,
also_print=False,
)
failed_count += 1 failed_count += 1
continue # No continuar si x3 falló continue # No continuar si x3 falló
@ -540,8 +747,15 @@ if __name__ == "__main__":
except Exception as e: except Exception as e:
# Capturar cualquier error inesperado durante las llamadas a funciones # Capturar cualquier error inesperado durante las llamadas a funciones
log_message(f"--- ERROR INESPERADO procesando {relative_path}: {e} ---", log_f, also_print=False) log_message(
print(f"--- ERROR INESPERADO procesando {relative_path}: {e} ---", file=sys.stderr) f"--- ERROR INESPERADO procesando {relative_path}: {e} ---",
log_f,
also_print=False,
)
print(
f"--- ERROR INESPERADO procesando {relative_path}: {e} ---",
file=sys.stderr,
)
traceback_str = traceback.format_exc() traceback_str = traceback.format_exc()
log_message(traceback_str, log_f, also_print=False) # Loguear traceback log_message(traceback_str, log_f, also_print=False) # Loguear traceback
traceback.print_exc(file=sys.stderr) # Mostrar traceback en consola traceback.print_exc(file=sys.stderr) # Mostrar traceback en consola
@ -549,7 +763,10 @@ if __name__ == "__main__":
continue # Pasar al siguiente archivo continue # Pasar al siguiente archivo
# <-- ADDED: Phase 1.5: Copy existing SCL files --> # <-- ADDED: Phase 1.5: Copy existing SCL files -->
log_message(f"\n--- Fase 1.5: Copiando archivos SCL existentes desde '{xml_project_dir}' a '{scl_output_dir}' ---", log_f) log_message(
f"\n--- Fase 1.5: Copiando archivos SCL existentes desde '{xml_project_dir}' a '{scl_output_dir}' ---",
log_f,
)
copied_scl_count = 0 copied_scl_count = 0
skipped_scl_count = 0 skipped_scl_count = 0
try: try:
@ -561,32 +778,61 @@ if __name__ == "__main__":
xref_output_dir_abs = os.path.abspath(xref_output_dir) xref_output_dir_abs = os.path.abspath(xref_output_dir)
filtered_scl_files = [ filtered_scl_files = [
f for f in existing_scl_files f
if not os.path.abspath(os.path.dirname(f)).startswith(scl_output_dir_abs) and \ for f in existing_scl_files
not os.path.abspath(os.path.dirname(f)).startswith(xref_output_dir_abs) if not os.path.abspath(os.path.dirname(f)).startswith(
scl_output_dir_abs
)
and not os.path.abspath(os.path.dirname(f)).startswith(
xref_output_dir_abs
)
] ]
if not filtered_scl_files: if not filtered_scl_files:
log_message("No se encontraron archivos .scl existentes para copiar (excluyendo directorios de salida).", log_f) log_message(
"No se encontraron archivos .scl existentes para copiar (excluyendo directorios de salida).",
log_f,
)
else: else:
log_message(f"Se encontraron {len(filtered_scl_files)} archivos .scl existentes para copiar:", log_f) log_message(
f"Se encontraron {len(filtered_scl_files)} archivos .scl existentes para copiar:",
log_f,
)
for src_scl_path in filtered_scl_files: for src_scl_path in filtered_scl_files:
relative_scl_path = os.path.relpath(src_scl_path, xml_project_dir) relative_scl_path = os.path.relpath(src_scl_path, xml_project_dir)
dest_scl_path = os.path.join(scl_output_dir, os.path.basename(src_scl_path)) # Copia directa al scl_output del PLC dest_scl_path = os.path.join(
scl_output_dir, os.path.basename(src_scl_path)
) # Copia directa al scl_output del PLC
# Check if a file with the same name was already generated from XML # Check if a file with the same name was already generated from XML
if os.path.exists(dest_scl_path): if os.path.exists(dest_scl_path):
log_message(f" - Omitiendo copia de '{relative_scl_path}': Ya existe un archivo generado con el mismo nombre en el destino.", log_f, also_print=False) log_message(
f" - Omitiendo copia de '{relative_scl_path}': Ya existe un archivo generado con el mismo nombre en el destino.",
log_f,
also_print=False,
)
skipped_scl_count += 1 skipped_scl_count += 1
else: else:
try: try:
log_message(f" - Copiando '{relative_scl_path}' a '{os.path.relpath(dest_scl_path, working_directory)}'", log_f, also_print=False) log_message(
shutil.copy2(src_scl_path, dest_scl_path) # copy2 preserves metadata f" - Copiando '{relative_scl_path}' a '{os.path.relpath(dest_scl_path, working_directory)}'",
log_f,
also_print=False,
)
shutil.copy2(
src_scl_path, dest_scl_path
) # copy2 preserves metadata
copied_scl_count += 1 copied_scl_count += 1
except Exception as copy_err: except Exception as copy_err:
log_message(f" - ERROR copiando '{relative_scl_path}': {copy_err}", log_f) log_message(
f" - ERROR copiando '{relative_scl_path}': {copy_err}",
log_f,
)
# Decide if this should count as a general failure # Decide if this should count as a general failure
log_message(f"Copia de SCL existentes finalizada. Copiados: {copied_scl_count}, Omitidos (conflicto nombre): {skipped_scl_count}", log_f) log_message(
f"Copia de SCL existentes finalizada. Copiados: {copied_scl_count}, Omitidos (conflicto nombre): {skipped_scl_count}",
log_f,
)
except Exception as e: except Exception as e:
log_message(f"Error durante la Fase 1.5 (Copia SCL): {e}", log_f) log_message(f"Error durante la Fase 1.5 (Copia SCL): {e}", log_f)
@ -629,13 +875,22 @@ if __name__ == "__main__":
cfg_db_usage_xref_filename, cfg_db_usage_xref_filename,
cfg_plc_tag_xref_filename, cfg_plc_tag_xref_filename,
cfg_max_call_depth, cfg_max_call_depth,
cfg_max_users_list) cfg_max_users_list,
)
if not success_x4: if not success_x4:
# La función interna ya debería haber impreso/logueado el error específico # La función interna ya debería haber impreso/logueado el error específico
log_message(f"--- x4 (generate_cross_references) FALLÓ. ---", log_f, also_print=False) log_message(
f"--- x4 (generate_cross_references) FALLÓ. ---",
log_f,
also_print=False,
)
except Exception as e: except Exception as e:
# Capturar error inesperado en la llamada a x4 # Capturar error inesperado en la llamada a x4
log_message(f"--- ERROR INESPERADO ejecutando x4: {e} ---", log_f, also_print=False) log_message(
f"--- ERROR INESPERADO ejecutando x4: {e} ---",
log_f,
also_print=False,
)
print(f"--- ERROR INESPERADO ejecutando x4: {e} ---", file=sys.stderr) print(f"--- ERROR INESPERADO ejecutando x4: {e} ---", file=sys.stderr)
traceback_str = traceback.format_exc() traceback_str = traceback.format_exc()
log_message(traceback_str, log_f, also_print=False) log_message(traceback_str, log_f, also_print=False)
@ -647,7 +902,7 @@ if __name__ == "__main__":
# --- PARTE 4: EJECUTAR x5 (Agregación) --- # --- PARTE 4: EJECUTAR x5 (Agregación) ---
log_message( log_message(
f"\n--- Fase 3: Ejecutando x5_aggregate.py (salida en '{cfg_aggregated_filename}') ---", # Usar valor de config f"\n--- Fase 3: Ejecutando x5_aggregate.py (salida en '{cfg_aggregated_filename}') ---", # Usar valor de config
log_f log_f,
) )
run_x5 = True run_x5 = True
success_x5 = False success_x5 = False
@ -655,7 +910,8 @@ if __name__ == "__main__":
can_run_x5 = failed_count < len(xml_files_found) can_run_x5 = failed_count < len(xml_files_found)
if not can_run_x5 and len(xml_files_found) > 0: if not can_run_x5 and len(xml_files_found) > 0:
log_message( log_message(
"Advertencia: Todos los archivos fallaron en x1/x2/x3. Saltando x5.", log_f "Advertencia: Todos los archivos fallaron en x1/x2/x3. Saltando x5.",
log_f,
) )
run_x5 = False run_x5 = False
elif len(xml_files_found) == 0: elif len(xml_files_found) == 0:
@ -666,7 +922,7 @@ if __name__ == "__main__":
output_agg_file = os.path.join(xml_project_dir, cfg_aggregated_filename) output_agg_file = os.path.join(xml_project_dir, cfg_aggregated_filename)
log_message( log_message(
f"Ejecutando x5 (aggregate_outputs) sobre: {xml_project_dir}, salida agregada en: {output_agg_file}", f"Ejecutando x5 (aggregate_outputs) sobre: {xml_project_dir}, salida agregada en: {output_agg_file}",
log_f log_f,
) )
try: try:
# Llamada directa a la función de x5 # Llamada directa a la función de x5
@ -675,13 +931,22 @@ if __name__ == "__main__":
xml_project_dir, xml_project_dir,
output_agg_file, output_agg_file,
cfg_scl_output_dirname, cfg_scl_output_dirname,
cfg_xref_output_dirname) cfg_xref_output_dirname,
)
if not success_x5: if not success_x5:
# La función interna ya debería haber impreso/logueado el error específico # La función interna ya debería haber impreso/logueado el error específico
log_message(f"--- x5 (aggregate_outputs) FALLÓ. ---", log_f, also_print=False) log_message(
f"--- x5 (aggregate_outputs) FALLÓ. ---",
log_f,
also_print=False,
)
except Exception as e: except Exception as e:
# Capturar error inesperado en la llamada a x5 # Capturar error inesperado en la llamada a x5
log_message(f"--- ERROR INESPERADO ejecutando x5: {e} ---", log_f, also_print=False) log_message(
f"--- ERROR INESPERADO ejecutando x5: {e} ---",
log_f,
also_print=False,
)
print(f"--- ERROR INESPERADO ejecutando x5: {e} ---", file=sys.stderr) print(f"--- ERROR INESPERADO ejecutando x5: {e} ---", file=sys.stderr)
traceback_str = traceback.format_exc() traceback_str = traceback.format_exc()
log_message(traceback_str, log_f, also_print=False) log_message(traceback_str, log_f, also_print=False)
@ -708,13 +973,21 @@ if __name__ == "__main__":
f"Archivos parcialmente saltados (x1, x2 saltados; x3 ejecutado): {skipped_partial_count}", f"Archivos parcialmente saltados (x1, x2 saltados; x3 ejecutado): {skipped_partial_count}",
log_f, log_f,
) )
log_message(f"Archivos fallidos (en x1, x2, x3 o error inesperado): {failed_count}", log_f) log_message(
f"Archivos fallidos (en x1, x2, x3 o error inesperado): {failed_count}",
log_f,
)
log_message( # <-- NUEVO: Reportar SCL nativos saltados log_message( # <-- NUEVO: Reportar SCL nativos saltados
f"Archivos XML omitidos (priorizando .scl nativo): {skipped_for_native_scl}", f"Archivos XML omitidos (priorizando .scl nativo): {skipped_for_native_scl}",
log_f, log_f,
) )
log_message(f"Archivos SCL existentes copiados (Fase 1.5): {copied_scl_count}", log_f) # <-- ADDED: Report copied SCL log_message(
log_message(f"Archivos SCL existentes omitidos por conflicto (Fase 1.5): {skipped_scl_count}", log_f) # <-- ADDED: Report skipped SCL f"Archivos SCL existentes copiados (Fase 1.5): {copied_scl_count}", log_f
) # <-- ADDED: Report copied SCL
log_message(
f"Archivos SCL existentes omitidos por conflicto (Fase 1.5): {skipped_scl_count}",
log_f,
) # <-- ADDED: Report skipped SCL
log_message( log_message(
f"Fase 2 (Generación XRef - x4): {'Completada' if run_x4 and success_x4 else ('Fallida' if run_x4 and not success_x4 else 'Omitida')}", f"Fase 2 (Generación XRef - x4): {'Completada' if run_x4 and success_x4 else ('Fallida' if run_x4 and not success_x4 else 'Omitida')}",
log_f, log_f,
@ -747,9 +1020,14 @@ if __name__ == "__main__":
# <-- NUEVO: Flush explícito antes de salir --> # <-- NUEVO: Flush explícito antes de salir -->
try: try:
log_f.flush() log_f.flush()
os.fsync(log_f.fileno()) # Intenta forzar escritura a disco (puede no funcionar en todos los OS) os.fsync(
log_f.fileno()
) # Intenta forzar escritura a disco (puede no funcionar en todos los OS)
except Exception as flush_err: except Exception as flush_err:
print(f"Advertencia: Error durante flush/fsync final del log: {flush_err}", file=sys.stderr) print(
f"Advertencia: Error durante flush/fsync final del log: {flush_err}",
file=sys.stderr,
)
# <-- FIN NUEVO --> # <-- FIN NUEVO -->
# Mensaje final ya impreso antes del flush # Mensaje final ya impreso antes del flush

View File

@ -0,0 +1,146 @@
"""
x8_manual_gui.py - Wrapper Simple para XMLSCL
Este script es un wrapper simple que:
1. Abre un cuadro de diálogo para seleccionar archivo XML
2. Abre un cuadro de diálogo para seleccionar directorio de destino
3. Ejecuta automáticamente x0_main.py en modo simple
No requiere GUI compleja, solo usa los cuadros de diálogo nativos de Windows.
"""
import tkinter as tk
from tkinter import filedialog, messagebox
import os
import sys
import subprocess
def main():
"""Función principal - Solo cuadros de diálogo y ejecución"""
# Configurar el directorio de script
script_dir = os.path.dirname(os.path.abspath(__file__))
x0_main_path = os.path.join(script_dir, "x0_main.py")
# Verificar que x0_main.py existe
if not os.path.exists(x0_main_path):
print(f"Error: No se encontró x0_main.py en: {x0_main_path}", file=sys.stderr)
sys.exit(1)
print("=== XML -> SCL Converter ===")
print("Selecciona el archivo XML y directorio de destino...")
print()
# Crear ventana oculta para los cuadros de diálogo
root = tk.Tk()
root.withdraw() # Ocultar la ventana principal
try:
# 1. Seleccionar archivo XML
print("1. Seleccionando archivo XML...")
source_xml = filedialog.askopenfilename(
title="Seleccionar archivo XML de Siemens TIA Portal",
filetypes=[("Archivos XML", "*.xml"), ("Todos los archivos", "*.*")],
initialdir=script_dir,
)
if not source_xml:
print("Operación cancelada por el usuario.")
sys.exit(0)
print(f" Archivo seleccionado: {os.path.basename(source_xml)}")
# 2. Seleccionar directorio de destino
print("2. Seleccionando directorio de destino...")
dest_dir = filedialog.askdirectory(
title="Seleccionar directorio de destino", initialdir=script_dir
)
if not dest_dir:
print("Operación cancelada por el usuario.")
sys.exit(0)
print(f" Directorio seleccionado: {dest_dir}")
# 3. Generar nombre de archivo de salida
base_name = os.path.splitext(os.path.basename(source_xml))[0]
dest_filename = f"{base_name}.scl"
dest_scl = os.path.join(dest_dir, dest_filename)
print(f"3. Archivo de salida: {dest_filename}")
print()
# 4. Confirmar operación
confirm = messagebox.askyesno(
"Confirmar Conversión",
f"¿Convertir el archivo?\n\n"
f"Origen: {os.path.basename(source_xml)}\n"
f"Destino: {dest_scl}\n\n"
f"¿Continuar?",
)
if not confirm:
print("Operación cancelada por el usuario.")
sys.exit(0)
# 5. Ejecutar conversión
print("4. Ejecutando conversión...")
print("=" * 50)
cmd = [
sys.executable,
x0_main_path,
"--source-xml",
source_xml,
"--dest-scl",
dest_scl,
]
# Ejecutar proceso
result = subprocess.run(
cmd,
cwd=script_dir,
capture_output=False, # Mostrar salida en tiempo real
text=True,
)
# 6. Verificar resultado
print("=" * 50)
if result.returncode == 0 and os.path.exists(dest_scl):
print("✅ CONVERSIÓN COMPLETADA EXITOSAMENTE")
print(f"📁 Archivo generado: {dest_scl}")
# Preguntar si abrir el directorio
open_dir = messagebox.askyesno(
"Conversión Exitosa",
f"La conversión se completó exitosamente.\n\n"
f"Archivo generado:\n{dest_scl}\n\n"
f"¿Deseas abrir el directorio de destino?",
)
if open_dir:
os.startfile(dest_dir)
else:
print("❌ ERROR EN LA CONVERSIÓN")
print("Revisa los mensajes de error arriba.")
messagebox.showerror(
"Error en Conversión",
"La conversión falló. Revisa los mensajes de error en la consola.",
)
except Exception as e:
print(f"❌ ERROR INESPERADO: {e}")
messagebox.showerror(
"Error Inesperado", f"Error inesperado durante la conversión:\n{e}"
)
sys.exit(1)
finally:
# Cerrar la ventana oculta
root.destroy()
if __name__ == "__main__":
main()

14938
data/log.txt

File diff suppressed because it is too large Load Diff