lasal-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/core/envelope.js +8 -0
- package/dist/core/errors.js +12 -0
- package/dist/core/http.js +19 -0
- package/dist/core/process.js +30 -0
- package/dist/core/response.js +37 -0
- package/dist/core/scratch.js +6 -0
- package/dist/core/staticServer.js +88 -0
- package/dist/server.js +230 -0
- package/dist/state.js +57 -0
- package/dist/tools/applyProjectChanges.js +371 -0
- package/dist/tools/deployAll.js +320 -0
- package/dist/tools/hmiBrowser.js +224 -0
- package/dist/tools/hmiRuntime.js +273 -0
- package/dist/tools/inspectProject.js +162 -0
- package/dist/tools/inspectVisuProject.js +474 -0
- package/dist/tools/larsRuntime.js +536 -0
- package/dist/tools/lasalApps.js +111 -0
- package/dist/tools/plcControl.js +530 -0
- package/dist/tools/plcDiagnostics.js +172 -0
- package/dist/tools/readClassSource.js +147 -0
- package/dist/tools/selectProject.js +47 -0
- package/dist/tools/setTargetIp.js +114 -0
- package/dist/tools/status.js +146 -0
- package/dist/tools/visuControl.js +447 -0
- package/dist/tools/visuDashboard.js +571 -0
- package/dist/utils/batchScript.js +257 -0
- package/dist/utils/config.js +34 -0
- package/dist/utils/editTransaction.js +39 -0
- package/dist/utils/engine.js +163 -0
- package/dist/utils/lars.js +471 -0
- package/dist/utils/lasalXml.js +758 -0
- package/dist/utils/preflight.js +194 -0
- package/dist/utils/projectScanner.js +212 -0
- package/dist/utils/resolvePaths.js +46 -0
- package/dist/utils/respond.js +14 -0
- package/dist/utils/scriptRunner.js +161 -0
- package/dist/utils/visuDashboardIO.js +198 -0
- package/dist/utils/visuPropertyEncoding.js +174 -0
- package/dist/utils/visuScript.js +262 -0
- package/package.json +64 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, writeFileSync, readFileSync, readdirSync } from "fs";
|
|
2
|
+
import { join, basename, dirname } from "path";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
// Generate UUID v4
|
|
5
|
+
export function newDesignTimeId() {
|
|
6
|
+
return randomUUID();
|
|
7
|
+
}
|
|
8
|
+
// Generate stripped instance ID
|
|
9
|
+
export function newInstanceId(prefix) {
|
|
10
|
+
const uuid = newDesignTimeId().replace(/-/g, "");
|
|
11
|
+
return `${prefix}${uuid}`;
|
|
12
|
+
}
|
|
13
|
+
// Read Real Version from .lvp
|
|
14
|
+
export function getFileEntryVersion(lvpPath, key) {
|
|
15
|
+
try {
|
|
16
|
+
if (existsSync(lvpPath)) {
|
|
17
|
+
const data = JSON.parse(readFileSync(lvpPath, "utf-8"));
|
|
18
|
+
if (data && data.fileEntryVersions && data.fileEntryVersions[key]) {
|
|
19
|
+
return data.fileEntryVersions[key];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch { }
|
|
24
|
+
return "0.0.96"; // standard fallback
|
|
25
|
+
}
|
|
26
|
+
// Backup file alongside original
|
|
27
|
+
const backedUpFiles = new Set();
|
|
28
|
+
export function backupFile(filePath) {
|
|
29
|
+
if (backedUpFiles.has(filePath))
|
|
30
|
+
return null;
|
|
31
|
+
if (!existsSync(filePath))
|
|
32
|
+
return null;
|
|
33
|
+
const dir = dirname(filePath);
|
|
34
|
+
const base = basename(filePath);
|
|
35
|
+
const ts = Date.now();
|
|
36
|
+
const backupPath = join(dir, `${base}.bak-${ts}`);
|
|
37
|
+
copyFileSync(filePath, backupPath);
|
|
38
|
+
backedUpFiles.add(filePath);
|
|
39
|
+
return backupPath;
|
|
40
|
+
}
|
|
41
|
+
// Resolve Text Reference: List:AliasName -> ID
|
|
42
|
+
export function resolveTextRef(projectDir, listName, aliasName) {
|
|
43
|
+
const locDir = join(projectDir, "Localization");
|
|
44
|
+
if (!existsSync(locDir))
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const langs = readdirSync(locDir);
|
|
48
|
+
for (const lang of langs) {
|
|
49
|
+
const listFile = join(locDir, lang, `${listName}.json`);
|
|
50
|
+
if (existsSync(listFile)) {
|
|
51
|
+
const content = readFileSync(listFile, "utf-8");
|
|
52
|
+
const data = JSON.parse(content);
|
|
53
|
+
if (data && Array.isArray(data.texts)) {
|
|
54
|
+
const item = data.texts.find((t) => t.aliasName === aliasName);
|
|
55
|
+
if (item && item.id)
|
|
56
|
+
return item.id;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
// Resolve Schemes/StateSchemes: Group:Scheme -> designTimeId
|
|
65
|
+
export function resolveSchemeRef(projectDir, schemeType, schemeName) {
|
|
66
|
+
const dir = join(projectDir, "Schemes", schemeType);
|
|
67
|
+
if (!existsSync(dir))
|
|
68
|
+
return null;
|
|
69
|
+
try {
|
|
70
|
+
for (const f of readdirSync(dir)) {
|
|
71
|
+
if (!f.endsWith(".json"))
|
|
72
|
+
continue;
|
|
73
|
+
const content = readFileSync(join(dir, f), "utf-8");
|
|
74
|
+
const data = JSON.parse(content);
|
|
75
|
+
if (data && Array.isArray(data.schemes)) {
|
|
76
|
+
const s = data.schemes.find((x) => x.name === schemeName);
|
|
77
|
+
if (s && s.designTimeId)
|
|
78
|
+
return s.designTimeId;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
// Resolve Color Reference: Palette:ColorName -> ID
|
|
86
|
+
export function resolveColorRef(projectDir, colorVal) {
|
|
87
|
+
if (!colorVal.includes(":"))
|
|
88
|
+
return null;
|
|
89
|
+
const [palette, name] = colorVal.split(":");
|
|
90
|
+
const path = join(projectDir, "Resources", `${palette}.colors`);
|
|
91
|
+
if (existsSync(path)) {
|
|
92
|
+
try {
|
|
93
|
+
const content = readFileSync(path, "utf-8");
|
|
94
|
+
const data = JSON.parse(content);
|
|
95
|
+
if (data && Array.isArray(data.colors)) {
|
|
96
|
+
const c = data.colors.find((x) => x.name === name);
|
|
97
|
+
if (c && c.id)
|
|
98
|
+
return c.id;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
// Resolve Font Reference: FontStyleName -> designTimeId
|
|
106
|
+
export function resolveFontRef(projectDir, fontName) {
|
|
107
|
+
const path = join(projectDir, "FontStyles", "FontStyles.json");
|
|
108
|
+
if (existsSync(path)) {
|
|
109
|
+
try {
|
|
110
|
+
const content = readFileSync(path, "utf-8");
|
|
111
|
+
const data = JSON.parse(content);
|
|
112
|
+
if (data && Array.isArray(data.fontStyles)) {
|
|
113
|
+
const f = data.fontStyles.find((x) => x.name === fontName);
|
|
114
|
+
if (f && f.designTimeId)
|
|
115
|
+
return f.designTimeId;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch { }
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
// Resolve StyleClass Reference: StyleClassName -> designTimeId
|
|
123
|
+
export function resolveStyleClassRef(projectDir, className) {
|
|
124
|
+
const path = join(projectDir, "DefaultStyles", "DefaultStyles.json");
|
|
125
|
+
if (!existsSync(path))
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
const content = readFileSync(path, "utf-8");
|
|
129
|
+
const data = JSON.parse(content);
|
|
130
|
+
if (data && Array.isArray(data.componentStyles)) {
|
|
131
|
+
const findInNode = (node) => {
|
|
132
|
+
if (node.name === className && node.designTimeId)
|
|
133
|
+
return node.designTimeId;
|
|
134
|
+
if (Array.isArray(node.children)) {
|
|
135
|
+
for (const child of node.children) {
|
|
136
|
+
const res = findInNode(child);
|
|
137
|
+
if (res)
|
|
138
|
+
return res;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
for (const style of data.componentStyles) {
|
|
144
|
+
const res = findInNode(style);
|
|
145
|
+
if (res)
|
|
146
|
+
return res;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch { }
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
// Resolve Template Reference: templateName -> designTimeId
|
|
154
|
+
export function resolveTemplateRef(projectDir, templateName) {
|
|
155
|
+
const path = join(projectDir, "ControlTemplate", `${templateName}.json`);
|
|
156
|
+
if (existsSync(path)) {
|
|
157
|
+
try {
|
|
158
|
+
const content = readFileSync(path, "utf-8");
|
|
159
|
+
const data = JSON.parse(content);
|
|
160
|
+
if (data && data.designTimeId)
|
|
161
|
+
return data.designTimeId;
|
|
162
|
+
}
|
|
163
|
+
catch { }
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
// Resolve Media Reference: mediaPath -> ID
|
|
168
|
+
export function resolveMediaRef(projectDir, mediaPath) {
|
|
169
|
+
const filename = basename(mediaPath.replace(/\\/g, "/"));
|
|
170
|
+
const resDir = join(projectDir, "Resources");
|
|
171
|
+
if (!existsSync(resDir))
|
|
172
|
+
return null;
|
|
173
|
+
try {
|
|
174
|
+
for (const sub of readdirSync(resDir)) {
|
|
175
|
+
const subPath = join(resDir, sub);
|
|
176
|
+
if (existsSync(subPath)) {
|
|
177
|
+
for (const file of readdirSync(subPath)) {
|
|
178
|
+
if (file.endsWith("Collection.json")) {
|
|
179
|
+
const content = readFileSync(join(subPath, file), "utf-8");
|
|
180
|
+
const data = JSON.parse(content);
|
|
181
|
+
if (data && Array.isArray(data.mediaItems)) {
|
|
182
|
+
const item = data.mediaItems.find((x) => x.name === filename);
|
|
183
|
+
if (item && item.id)
|
|
184
|
+
return item.id;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch { }
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
// Tab indented JSON output
|
|
195
|
+
export function writeTabIndentedJson(filePath, data) {
|
|
196
|
+
const content = JSON.stringify(data, null, "\t");
|
|
197
|
+
writeFileSync(filePath, content, "utf-8");
|
|
198
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { newDesignTimeId, resolveTextRef, resolveSchemeRef, resolveColorRef, resolveFontRef, resolveStyleClassRef, resolveTemplateRef, resolveMediaRef, } from "./visuDashboardIO.js";
|
|
4
|
+
// Clean JSON helper for manifests (strips comments & trailing commas)
|
|
5
|
+
export function cleanJson(content) {
|
|
6
|
+
content = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
7
|
+
content = content.replace(/^[ \t]*\/\/.*$/gm, "");
|
|
8
|
+
content = content.replace(/,(\s*[\]}])/g, "$1");
|
|
9
|
+
return content;
|
|
10
|
+
}
|
|
11
|
+
// Load control manifest, merging inherited properties
|
|
12
|
+
const manifestCache = {};
|
|
13
|
+
export function loadControlManifest(projectDir, controlId) {
|
|
14
|
+
if (manifestCache[controlId])
|
|
15
|
+
return manifestCache[controlId];
|
|
16
|
+
const userPath = join(projectDir, "Runtime", "DesignerRuntime", "res", "components", "user", controlId, `${controlId}.json`);
|
|
17
|
+
const sigmatekPath = join(projectDir, "Runtime", "DesignerRuntime", "res", "components", "sigmatek", controlId, `${controlId}.json`);
|
|
18
|
+
let manifest = null;
|
|
19
|
+
if (existsSync(userPath)) {
|
|
20
|
+
manifest = JSON.parse(cleanJson(readFileSync(userPath, "utf-8")));
|
|
21
|
+
}
|
|
22
|
+
else if (existsSync(sigmatekPath)) {
|
|
23
|
+
manifest = JSON.parse(cleanJson(readFileSync(sigmatekPath, "utf-8")));
|
|
24
|
+
}
|
|
25
|
+
if (!manifest)
|
|
26
|
+
return null;
|
|
27
|
+
// Resolve base Component properties recursively
|
|
28
|
+
if (manifest.baseComponent && manifest.baseComponent !== controlId) {
|
|
29
|
+
const parent = loadControlManifest(projectDir, manifest.baseComponent);
|
|
30
|
+
if (parent && parent.properties) {
|
|
31
|
+
const mergedProps = [...(parent.properties || [])];
|
|
32
|
+
for (const prop of manifest.properties || []) {
|
|
33
|
+
const idx = mergedProps.findIndex((p) => p.name === prop.name);
|
|
34
|
+
if (idx >= 0)
|
|
35
|
+
mergedProps[idx] = prop;
|
|
36
|
+
else
|
|
37
|
+
mergedProps.push(prop);
|
|
38
|
+
}
|
|
39
|
+
manifest.properties = mergedProps;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
manifestCache[controlId] = manifest;
|
|
43
|
+
return manifest;
|
|
44
|
+
}
|
|
45
|
+
// Format UUID function block ID
|
|
46
|
+
export function resolveFunctionBlockRef(projectDir, fbName) {
|
|
47
|
+
const path = join(projectDir, "Functionblocks", `${fbName}.json`);
|
|
48
|
+
if (existsSync(path)) {
|
|
49
|
+
try {
|
|
50
|
+
const data = JSON.parse(cleanJson(readFileSync(path, "utf-8")));
|
|
51
|
+
if (data && data.id) {
|
|
52
|
+
let idStr = data.id;
|
|
53
|
+
if (idStr.startsWith("_")) {
|
|
54
|
+
idStr = idStr.substring(1);
|
|
55
|
+
}
|
|
56
|
+
if (idStr.length === 32) {
|
|
57
|
+
return `${idStr.substring(0, 8)}-${idStr.substring(8, 12)}-${idStr.substring(12, 16)}-${idStr.substring(16, 20)}-${idStr.substring(20)}`;
|
|
58
|
+
}
|
|
59
|
+
return idStr;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Encode property based on manifest entry and sourceType
|
|
67
|
+
export function encodeProperty(projectDir, propName, manifestProp, sourceType, value) {
|
|
68
|
+
const propType = manifestProp ? manifestProp.propertyType || "none" : "none";
|
|
69
|
+
let typeId = 0;
|
|
70
|
+
let propTypeId = 2;
|
|
71
|
+
let refId;
|
|
72
|
+
switch (sourceType) {
|
|
73
|
+
case "constString":
|
|
74
|
+
case "constNumber":
|
|
75
|
+
case "constBool":
|
|
76
|
+
if (propType === "css") {
|
|
77
|
+
if (propName.startsWith("--theme-sig-element-")) {
|
|
78
|
+
typeId = 17;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
typeId = 0;
|
|
82
|
+
}
|
|
83
|
+
propTypeId = 2;
|
|
84
|
+
}
|
|
85
|
+
else if (propType === "rotation") {
|
|
86
|
+
typeId = 17;
|
|
87
|
+
propTypeId = 12;
|
|
88
|
+
}
|
|
89
|
+
else if (propType === "variable") {
|
|
90
|
+
typeId = 0;
|
|
91
|
+
propTypeId = 1;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
typeId = 0;
|
|
95
|
+
propTypeId = 2;
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case "datapoint":
|
|
99
|
+
typeId = 4;
|
|
100
|
+
propTypeId = 1;
|
|
101
|
+
refId = newDesignTimeId(); // freestanding UUID
|
|
102
|
+
if (typeof value === "string" && !value.includes(":")) {
|
|
103
|
+
value = `0:${value}`;
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case "text":
|
|
107
|
+
typeId = 5;
|
|
108
|
+
propTypeId = 1;
|
|
109
|
+
if (typeof value === "string" && value.includes(":")) {
|
|
110
|
+
const [listName, aliasName] = value.split(":");
|
|
111
|
+
refId = resolveTextRef(projectDir, listName, aliasName) || undefined;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case "colorScheme":
|
|
115
|
+
// If it contains a colon, check if it resolves as a palette color first, then as colorScheme
|
|
116
|
+
if (typeof value === "string" && value.includes(":")) {
|
|
117
|
+
const paletteColorId = resolveColorRef(projectDir, value);
|
|
118
|
+
if (paletteColorId) {
|
|
119
|
+
typeId = 9;
|
|
120
|
+
propTypeId = 2;
|
|
121
|
+
refId = paletteColorId;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
typeId = 18;
|
|
126
|
+
propTypeId = 2;
|
|
127
|
+
refId = resolveSchemeRef(projectDir, "ColorSchemes", String(value)) || undefined;
|
|
128
|
+
break;
|
|
129
|
+
case "stateScheme":
|
|
130
|
+
typeId = 21;
|
|
131
|
+
propTypeId = 1;
|
|
132
|
+
refId = resolveSchemeRef(projectDir, "StateSchemes", String(value)) || undefined;
|
|
133
|
+
break;
|
|
134
|
+
case "functionBlock":
|
|
135
|
+
typeId = 1;
|
|
136
|
+
propTypeId = 3;
|
|
137
|
+
refId = resolveFunctionBlockRef(projectDir, String(value)) || undefined;
|
|
138
|
+
break;
|
|
139
|
+
case "compositeControl":
|
|
140
|
+
typeId = 23;
|
|
141
|
+
propTypeId = 5;
|
|
142
|
+
refId = resolveTemplateRef(projectDir, String(value)) || undefined;
|
|
143
|
+
break;
|
|
144
|
+
case "imageOrMedia":
|
|
145
|
+
typeId = 15;
|
|
146
|
+
propTypeId = 2;
|
|
147
|
+
refId = resolveMediaRef(projectDir, String(value)) || undefined;
|
|
148
|
+
break;
|
|
149
|
+
case "fontStyle":
|
|
150
|
+
typeId = 26;
|
|
151
|
+
propTypeId = 6;
|
|
152
|
+
refId = resolveFontRef(projectDir, String(value)) || undefined;
|
|
153
|
+
break;
|
|
154
|
+
case "styleClass":
|
|
155
|
+
typeId = 34;
|
|
156
|
+
propTypeId = 14;
|
|
157
|
+
refId = resolveStyleClassRef(projectDir, String(value)) || undefined;
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
// Fallback
|
|
161
|
+
typeId = 0;
|
|
162
|
+
propTypeId = 2;
|
|
163
|
+
}
|
|
164
|
+
const result = {
|
|
165
|
+
name: propName,
|
|
166
|
+
value,
|
|
167
|
+
typeId,
|
|
168
|
+
propTypeId,
|
|
169
|
+
};
|
|
170
|
+
if (refId) {
|
|
171
|
+
result.refId = refId;
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { VISUDESIGNER_EXE, SCRATCH, killVisuDesigner } from "./engine.js";
|
|
5
|
+
import { runEngineScript } from "./scriptRunner.js";
|
|
6
|
+
import { ensureScratch } from "../core/scratch.js";
|
|
7
|
+
function emitStr(s) {
|
|
8
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
|
|
9
|
+
}
|
|
10
|
+
function emitPyScalar(v) {
|
|
11
|
+
if (v === null || v === undefined)
|
|
12
|
+
return "None";
|
|
13
|
+
if (typeof v === "boolean")
|
|
14
|
+
return v ? "True" : "False";
|
|
15
|
+
if (typeof v === "number")
|
|
16
|
+
return String(v);
|
|
17
|
+
return emitStr(String(v));
|
|
18
|
+
}
|
|
19
|
+
function emitStrList(arr) {
|
|
20
|
+
return `[${arr.map(emitStr).join(", ")}]`;
|
|
21
|
+
}
|
|
22
|
+
function emitOptStrList(arr) {
|
|
23
|
+
return arr && arr.length > 0 ? emitStrList(arr) : "None";
|
|
24
|
+
}
|
|
25
|
+
function emitTextElementsForEntry(entry) {
|
|
26
|
+
const { id, ...langs } = entry;
|
|
27
|
+
const langKeys = Object.keys(langs).filter((k) => langs[k] !== undefined);
|
|
28
|
+
if (langKeys.length === 0) {
|
|
29
|
+
return [`lvd.TextElement(${emitStr(id)})`];
|
|
30
|
+
}
|
|
31
|
+
return langKeys.map((lang) => `lvd.TextElement(${emitStr(id)}, ${emitStr(lang)}, ${emitStr(langs[lang])})`);
|
|
32
|
+
}
|
|
33
|
+
function emitTextList(def) {
|
|
34
|
+
let elements = "None";
|
|
35
|
+
if (def.texts && def.texts.length > 0) {
|
|
36
|
+
const elems = def.texts.flatMap(emitTextElementsForEntry);
|
|
37
|
+
elements = `[${elems.join(", ")}]`;
|
|
38
|
+
}
|
|
39
|
+
const rev = def.revision ? emitStr(def.revision) : "None";
|
|
40
|
+
return `lvd.TextList(${emitStr(def.name)}, ${elements}, ${rev})`;
|
|
41
|
+
}
|
|
42
|
+
function emitTextListForRemove(def) {
|
|
43
|
+
const elems = def.ids.map((id) => `lvd.TextElement(${emitStr(id)})`).join(", ");
|
|
44
|
+
return `lvd.TextList(${emitStr(def.name)}, [${elems}], None)`;
|
|
45
|
+
}
|
|
46
|
+
function emitPropertySet(def) {
|
|
47
|
+
return `lvd.PropertySet(${emitStr(def.element)}, ${emitStr(def.property)}, ${emitPyScalar(def.value)})`;
|
|
48
|
+
}
|
|
49
|
+
function emitSchemeEntry(e) {
|
|
50
|
+
const prop = e.property ? emitStr(e.property) : "None";
|
|
51
|
+
const val = emitPyScalar(e.value ?? null);
|
|
52
|
+
const newPos = e.new_position !== undefined ? String(e.new_position) : "None";
|
|
53
|
+
return `lvd.SchemeEntry(${e.position}, ${prop}, ${val}, ${newPos})`;
|
|
54
|
+
}
|
|
55
|
+
function emitSchemeFull(def) {
|
|
56
|
+
const inp = def.input ? emitStr(def.input) : "None";
|
|
57
|
+
let entries = "None";
|
|
58
|
+
if (def.entries && def.entries.length > 0) {
|
|
59
|
+
entries = `[${def.entries.map(emitSchemeEntry).join(", ")}]`;
|
|
60
|
+
}
|
|
61
|
+
return `lvd.Scheme(${emitStr(def.scheme_type)}, ${emitStr(def.name)}, ${inp}, ${entries})`;
|
|
62
|
+
}
|
|
63
|
+
function emitSchemeForRemove(def) {
|
|
64
|
+
return `lvd.Scheme(${emitStr(def.scheme_type)}, ${emitStr(def.name)}, None, None)`;
|
|
65
|
+
}
|
|
66
|
+
// Positions or entries to remove
|
|
67
|
+
function emitSchemeForRemoveEntries(def) {
|
|
68
|
+
const positions = def.positions ?? def.entries?.map((e) => e.position) ?? [];
|
|
69
|
+
const elems = positions.map((p) => `lvd.SchemeEntry(${p})`).join(", ");
|
|
70
|
+
return `lvd.Scheme(${emitStr(def.scheme_type)}, ${emitStr(def.name)}, None, [${elems}])`;
|
|
71
|
+
}
|
|
72
|
+
function emitSchemeForMoves(def) {
|
|
73
|
+
const moves = def.moves ?? [];
|
|
74
|
+
const elems = moves.map((m) => `lvd.SchemeEntry(${m.from}, None, None, ${m.to})`).join(", ");
|
|
75
|
+
return `lvd.Scheme(${emitStr(def.scheme_type)}, ${emitStr(def.name)}, None, [${elems}])`;
|
|
76
|
+
}
|
|
77
|
+
function emitSchemeForInput(def) {
|
|
78
|
+
const inp = def.input ? emitStr(def.input) : "None";
|
|
79
|
+
return `lvd.Scheme(${emitStr(def.scheme_type)}, ${emitStr(def.name)}, ${inp}, None)`;
|
|
80
|
+
}
|
|
81
|
+
function emitMediaItem(def) {
|
|
82
|
+
return `lvd.MediaItem(${emitStr(def.media_type)}, ${emitStr(def.path ?? def.name ?? "")})`;
|
|
83
|
+
}
|
|
84
|
+
export function buildVisuScript(lvpPath, ops, logPath, stepsPath, saveAtEnd = true) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
const expectedSteps = ["LoadProject"];
|
|
87
|
+
lines.push(' f_step = open(r"' + stepsPath + '", "a", encoding="utf-8")', ' f_step.write("STEP LoadProject OK\\n")', " f_step.close()", "");
|
|
88
|
+
for (let i = 0; i < ops.length; i++) {
|
|
89
|
+
const op = ops[i];
|
|
90
|
+
if (!op)
|
|
91
|
+
continue;
|
|
92
|
+
const label = `${i}_${op.type}`;
|
|
93
|
+
expectedSteps.push(label);
|
|
94
|
+
const opLines = [];
|
|
95
|
+
switch (op.type) {
|
|
96
|
+
case "update_all_stations":
|
|
97
|
+
opLines.push("lvd.UpdateAllStations(prj)");
|
|
98
|
+
break;
|
|
99
|
+
case "update_station":
|
|
100
|
+
opLines.push(`lvd.UpdateStation(prj, ${op.station_nr})`);
|
|
101
|
+
break;
|
|
102
|
+
case "publish":
|
|
103
|
+
opLines.push(`lvd.PublishProject(prj, ${op.debug ? "True" : "False"})`);
|
|
104
|
+
break;
|
|
105
|
+
case "add_text_lists":
|
|
106
|
+
opLines.push(`lvd.AddTextLists(prj, [${op.text_lists.map(emitTextList).join(", ")}])`);
|
|
107
|
+
break;
|
|
108
|
+
case "remove_text_lists":
|
|
109
|
+
opLines.push(`lvd.RemoveTextLists(prj, ${emitStrList(op.names)})`);
|
|
110
|
+
break;
|
|
111
|
+
case "add_texts":
|
|
112
|
+
opLines.push(`lvd.AddTexts(prj, [${op.text_lists.map(emitTextList).join(", ")}])`);
|
|
113
|
+
break;
|
|
114
|
+
case "remove_texts":
|
|
115
|
+
opLines.push(`lvd.RemoveTexts(prj, [${op.text_lists.map(emitTextListForRemove).join(", ")}])`);
|
|
116
|
+
break;
|
|
117
|
+
case "change_texts":
|
|
118
|
+
opLines.push(`lvd.ChangeTexts(prj, [${op.text_lists.map(emitTextList).join(", ")}])`);
|
|
119
|
+
break;
|
|
120
|
+
case "change_component_texts":
|
|
121
|
+
opLines.push(`lvd.ChangeComponentTexts(prj, [${op.text_lists.map(emitTextList).join(", ")}])`);
|
|
122
|
+
break;
|
|
123
|
+
case "set_text_list_revisions":
|
|
124
|
+
opLines.push(`lvd.SetTextListRevisions(prj, [${op.text_lists.map(emitTextList).join(", ")}])`);
|
|
125
|
+
break;
|
|
126
|
+
case "set_component_text_list_revisions":
|
|
127
|
+
opLines.push(`lvd.SetComponentTextListRevisions(prj, [${op.text_lists.map(emitTextList).join(", ")}])`);
|
|
128
|
+
break;
|
|
129
|
+
case "csv_export_text_lists":
|
|
130
|
+
opLines.push(`lvd.CsvExportTextLists(prj, ${emitStr(op.csv_path)}, ${emitOptStrList(op.text_lists)}, ${emitOptStrList(op.languages)})`);
|
|
131
|
+
break;
|
|
132
|
+
case "csv_import_text_lists":
|
|
133
|
+
opLines.push(`lvd.CsvImportTextLists(prj, ${emitStrList(op.file_paths)}, ${emitOptStrList(op.text_lists)}, ${emitOptStrList(op.languages)})`);
|
|
134
|
+
break;
|
|
135
|
+
case "csv_export_component_text_lists":
|
|
136
|
+
opLines.push(`lvd.CsvExportComponentTextLists(prj, ${emitStr(op.csv_path)}, ${emitOptStrList(op.text_lists)}, ${emitOptStrList(op.languages)})`);
|
|
137
|
+
break;
|
|
138
|
+
case "csv_import_component_text_lists":
|
|
139
|
+
opLines.push(`lvd.CsvImportComponentTextLists(prj, ${emitStrList(op.file_paths)}, ${emitOptStrList(op.text_lists)}, ${emitOptStrList(op.languages)})`);
|
|
140
|
+
break;
|
|
141
|
+
case "set_datapoint_properties":
|
|
142
|
+
opLines.push(`lvd.SetDatapointProperties(prj, [${op.properties.map(emitPropertySet).join(", ")}])`);
|
|
143
|
+
break;
|
|
144
|
+
case "set_datatype_properties":
|
|
145
|
+
opLines.push(`lvd.SetDataTypeProperties(prj, [${op.properties.map(emitPropertySet).join(", ")}])`);
|
|
146
|
+
break;
|
|
147
|
+
case "add_schemes":
|
|
148
|
+
opLines.push(`lvd.AddSchemes(prj, [${op.schemes.map(emitSchemeFull).join(", ")}])`);
|
|
149
|
+
break;
|
|
150
|
+
case "remove_schemes":
|
|
151
|
+
opLines.push(`lvd.RemoveSchemes(prj, [${op.schemes.map(emitSchemeForRemove).join(", ")}])`);
|
|
152
|
+
break;
|
|
153
|
+
case "add_scheme_entries":
|
|
154
|
+
opLines.push(`lvd.AddSchemeEntries(prj, [${op.schemes.map(emitSchemeFull).join(", ")}])`);
|
|
155
|
+
break;
|
|
156
|
+
case "remove_scheme_entries":
|
|
157
|
+
opLines.push(`lvd.RemoveSchemeEntries(prj, [${op.schemes.map(emitSchemeForRemoveEntries).join(", ")}])`);
|
|
158
|
+
break;
|
|
159
|
+
case "move_scheme_entries":
|
|
160
|
+
opLines.push(`lvd.MoveSchemeEntries(prj, [${op.schemes.map(emitSchemeForMoves).join(", ")}])`);
|
|
161
|
+
break;
|
|
162
|
+
case "set_scheme_inputs":
|
|
163
|
+
opLines.push(`lvd.SetSchemeInputs(prj, [${op.schemes.map(emitSchemeForInput).join(", ")}])`);
|
|
164
|
+
break;
|
|
165
|
+
case "set_scheme_properties":
|
|
166
|
+
opLines.push(`lvd.SetSchemeProperties(prj, ${emitStr(op.scheme_type)}, [${op.properties.map(emitPropertySet).join(", ")}])`);
|
|
167
|
+
break;
|
|
168
|
+
case "set_scheme_entry_properties":
|
|
169
|
+
opLines.push(`lvd.SetSchemeEntryProperties(prj, [${op.schemes.map(emitSchemeFull).join(", ")}])`);
|
|
170
|
+
break;
|
|
171
|
+
case "add_media_items": {
|
|
172
|
+
const overwrite = op.overwrite ? "True" : "False";
|
|
173
|
+
opLines.push(`lvd.AddMediaItems(prj, [${op.items.map(emitMediaItem).join(", ")}], ${overwrite})`);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "remove_media_items":
|
|
177
|
+
opLines.push(`lvd.RemoveMediaItems(prj, [${op.items.map(emitMediaItem).join(", ")}])`);
|
|
178
|
+
break;
|
|
179
|
+
case "add_code_modules":
|
|
180
|
+
opLines.push(`lvd.AddCodeModules(prj, ${emitStrList(op.paths)})`);
|
|
181
|
+
break;
|
|
182
|
+
case "remove_code_modules":
|
|
183
|
+
opLines.push(`lvd.RemoveCodeModules(prj, ${emitStrList(op.names)})`);
|
|
184
|
+
break;
|
|
185
|
+
case "update_property_values":
|
|
186
|
+
opLines.push("lvd.UpdatePropertyValues(prj)");
|
|
187
|
+
break;
|
|
188
|
+
case "download": {
|
|
189
|
+
const flags = op.flags ?? 0;
|
|
190
|
+
const addRuntime = op.add_runtime ? "True" : "False";
|
|
191
|
+
opLines.push(`lvd.DownloadProject(prj, ${emitStr(op.connection)}, ${flags}, ${addRuntime})`);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const opLine of opLines) {
|
|
196
|
+
lines.push(` ${opLine}`);
|
|
197
|
+
}
|
|
198
|
+
lines.push(` f_step = open(r"${stepsPath}", "a", encoding="utf-8")`, ` f_step.write("STEP ${label} OK\\n")`, ` f_step.close()`, "");
|
|
199
|
+
}
|
|
200
|
+
if (saveAtEnd) {
|
|
201
|
+
expectedSteps.push("SaveProject");
|
|
202
|
+
lines.push(" lvd.SaveProject(prj)", ` f_step = open(r"${stepsPath}", "a", encoding="utf-8")`, ` f_step.write("STEP SaveProject OK\\n")`, ` f_step.close()`, "");
|
|
203
|
+
}
|
|
204
|
+
expectedSteps.push("CloseProject");
|
|
205
|
+
lines.push(" lvd.CloseProject(prj)", ` f_step = open(r"${stepsPath}", "a", encoding="utf-8")`, ` f_step.write("STEP CloseProject OK\\n")`, ` f_step.close()`, "");
|
|
206
|
+
const finalScript = [
|
|
207
|
+
"import sys",
|
|
208
|
+
"import traceback",
|
|
209
|
+
`log_file = open(${emitStr(logPath)}, "w", encoding="utf-8")`,
|
|
210
|
+
"sys.stdout = log_file",
|
|
211
|
+
"sys.stderr = log_file",
|
|
212
|
+
"",
|
|
213
|
+
"import sigmatek.lasal.lvd as lvd",
|
|
214
|
+
"lvd.SetExceptionOnError(True)",
|
|
215
|
+
"",
|
|
216
|
+
"try:",
|
|
217
|
+
` prj = lvd.LoadProject(${emitStr(lvpPath)})`,
|
|
218
|
+
" if prj is None: raise RuntimeError('Failed to load project')",
|
|
219
|
+
"",
|
|
220
|
+
lines.join("\n"),
|
|
221
|
+
"except Exception as e:",
|
|
222
|
+
" print('(ERROR) Exception occurred during script execution:')",
|
|
223
|
+
" traceback.print_exc()",
|
|
224
|
+
" sys.exit(1)",
|
|
225
|
+
"finally:",
|
|
226
|
+
" log_file.close()",
|
|
227
|
+
].join("\n") + "\n";
|
|
228
|
+
return {
|
|
229
|
+
script: finalScript,
|
|
230
|
+
expectedSteps,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export async function runVisuOps(lvpPath, ops, saveAtEnd = true, timeoutMs = 180_000) {
|
|
234
|
+
ensureScratch();
|
|
235
|
+
const id = randomUUID();
|
|
236
|
+
const scriptPath = join(SCRATCH, `visu_${id}.py`);
|
|
237
|
+
const logPath = join(SCRATCH, `visu_${id}.log`);
|
|
238
|
+
const stepsPath = join(SCRATCH, `visu_${id}.steps`);
|
|
239
|
+
const { script, expectedSteps } = buildVisuScript(lvpPath, ops, logPath, stepsPath, saveAtEnd);
|
|
240
|
+
writeFileSync(scriptPath, script, "utf-8");
|
|
241
|
+
killVisuDesigner();
|
|
242
|
+
const result = await runEngineScript(scriptPath, {
|
|
243
|
+
exe: VISUDESIGNER_EXE,
|
|
244
|
+
argsFor: (p) => ["--script", p],
|
|
245
|
+
timeoutMs,
|
|
246
|
+
logEncoding: "utf-8",
|
|
247
|
+
killOnFailure: killVisuDesigner,
|
|
248
|
+
expectedSteps,
|
|
249
|
+
stepsPath,
|
|
250
|
+
}, logPath);
|
|
251
|
+
return {
|
|
252
|
+
ok: result.ok,
|
|
253
|
+
exitCode: result.exitCode,
|
|
254
|
+
logPath: result.logPath,
|
|
255
|
+
errors: result.errors,
|
|
256
|
+
warnings: result.warnings,
|
|
257
|
+
durationMs: result.durationMs,
|
|
258
|
+
steps: result.steps,
|
|
259
|
+
timedOut: result.timedOut,
|
|
260
|
+
hints: result.hints,
|
|
261
|
+
};
|
|
262
|
+
}
|