@codesys/mcp-toolkit 2.2.0 → 2.3.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/CHANGELOG.md +85 -0
- package/README.md +109 -9
- package/README.zh-CN.md +72 -9
- package/dist/actions.d.ts +9 -4
- package/dist/actions.js +1 -1
- package/dist/actions.js.map +1 -1
- package/dist/codesys/debug.d.ts +5 -0
- package/dist/codesys/debug.js +89 -0
- package/dist/codesys/debug.js.map +1 -0
- package/dist/codesys/device-tools.d.ts +3 -0
- package/dist/codesys/device-tools.js +38 -0
- package/dist/codesys/device-tools.js.map +1 -0
- package/dist/codesys/graphical.d.ts +76 -0
- package/dist/codesys/graphical.js +226 -0
- package/dist/codesys/graphical.js.map +1 -0
- package/dist/codesys/lang.d.ts +39 -0
- package/dist/codesys/lang.js +177 -0
- package/dist/codesys/lang.js.map +1 -0
- package/dist/codesys/simtest.d.ts +48 -0
- package/dist/codesys/simtest.js +103 -0
- package/dist/codesys/simtest.js.map +1 -0
- package/dist/codesys/st.d.ts +1 -1
- package/dist/codesys/st.js.map +1 -1
- package/dist/codesys/trace.d.ts +19 -0
- package/dist/codesys/trace.js +139 -0
- package/dist/codesys/trace.js.map +1 -0
- package/dist/hmi/native.d.ts +89 -0
- package/dist/hmi/native.js +447 -0
- package/dist/hmi/native.js.map +1 -0
- package/dist/hmi/render.d.ts +90 -0
- package/dist/hmi/render.js +307 -0
- package/dist/hmi/render.js.map +1 -0
- package/dist/hmi/route.d.ts +32 -0
- package/dist/hmi/route.js +184 -0
- package/dist/hmi/route.js.map +1 -0
- package/dist/hmi/service.d.ts +45 -0
- package/dist/hmi/service.js +182 -0
- package/dist/hmi/service.js.map +1 -0
- package/dist/hmi/spec.d.ts +821 -0
- package/dist/hmi/spec.js +191 -0
- package/dist/hmi/spec.js.map +1 -0
- package/dist/hmi/templates.d.ts +11 -0
- package/dist/hmi/templates.js +92 -0
- package/dist/hmi/templates.js.map +1 -0
- package/dist/hmi/tools.d.ts +4 -0
- package/dist/hmi/tools.js +222 -0
- package/dist/hmi/tools.js.map +1 -0
- package/dist/hmi/value.d.ts +3 -0
- package/dist/hmi/value.js +26 -0
- package/dist/hmi/value.js.map +1 -0
- package/dist/panel/hmi.js +138 -0
- package/dist/panel.d.ts +3 -0
- package/dist/panel.js +17 -0
- package/dist/panel.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +7 -2
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts +5 -1
- package/dist/tools.js +18 -2
- package/dist/tools.js.map +1 -1
- package/dist/viewer.d.ts +1 -1
- package/dist/viewer.js +13 -2
- package/dist/viewer.js.map +1 -1
- package/package.json +1 -1
- package/watcher/mcp_alarms.py +134 -0
- package/watcher/mcp_debug.py +161 -0
- package/watcher/mcp_devices.py +83 -0
- package/watcher/mcp_lang.py +179 -0
- package/watcher/mcp_ops.py +3 -1
- package/watcher/mcp_trace.py +90 -0
- package/watcher/mcp_ui.py +54 -0
- package/watcher/mcp_visu.py +422 -0
- package/watcher/watcher.py +35 -1
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Native CODESYS visualizations, built from a plan the server computes (src/hmi/native.ts).
|
|
3
|
+
#
|
|
4
|
+
# Two routes, both CODESYS's own:
|
|
5
|
+
# - the visualization script driver (create_visualobject, add_element, set_property with
|
|
6
|
+
# property-view paths) for image switchers, bindings, colour toggles, movement, inputs;
|
|
7
|
+
# - CODESYS's element creators (the objects its toolbox uses) for what the property paths
|
|
8
|
+
# can't set: a static font, a gradient, a hidden frame or fill. Added through CODESYS's
|
|
9
|
+
# object manager, like the toolbox does.
|
|
10
|
+
# Rounded corners and the canvas background are members of CODESYS's element objects that
|
|
11
|
+
# were checked alone. Nothing here writes other raw members (some raise assertion dialogs).
|
|
12
|
+
# A property that fails is reported, not fatal. render paints a visualization into a PNG.
|
|
13
|
+
#
|
|
14
|
+
# Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
import mcp_ops
|
|
18
|
+
from mcp_ops import Ctx, OpError, name_of
|
|
19
|
+
from mcp_compat import to_text
|
|
20
|
+
|
|
21
|
+
import scriptengine as se
|
|
22
|
+
|
|
23
|
+
_CACHE = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _find_type(name):
|
|
27
|
+
if ("t", name) in _CACHE:
|
|
28
|
+
return _CACHE[("t", name)]
|
|
29
|
+
from System import AppDomain
|
|
30
|
+
found = None
|
|
31
|
+
for asm in AppDomain.CurrentDomain.GetAssemblies():
|
|
32
|
+
try:
|
|
33
|
+
found = asm.GetType(name)
|
|
34
|
+
except Exception:
|
|
35
|
+
found = None
|
|
36
|
+
if found is not None:
|
|
37
|
+
break
|
|
38
|
+
_CACHE[("t", name)] = found
|
|
39
|
+
return found
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _static(type_name, prop):
|
|
43
|
+
from System.Reflection import BindingFlags
|
|
44
|
+
t = _find_type(type_name)
|
|
45
|
+
if t is None:
|
|
46
|
+
return None
|
|
47
|
+
p = t.GetProperty(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
|
|
48
|
+
return p.GetValue(None, None) if p is not None else None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _object_mgr():
|
|
52
|
+
for t in ("_3S.CoDeSys.VisualEditor.APEnvironment", "_3S.CoDeSys.SecurityManager.APEnvironment"):
|
|
53
|
+
m = _static(t, "ObjectMgr")
|
|
54
|
+
if m is not None:
|
|
55
|
+
return m
|
|
56
|
+
raise OpError("unavailable", "CODESYS's object manager isn't reachable.")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _drawing():
|
|
60
|
+
import clr
|
|
61
|
+
clr.AddReference("System.Drawing")
|
|
62
|
+
import System.Drawing as SD
|
|
63
|
+
return SD
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _argb(v):
|
|
67
|
+
import System
|
|
68
|
+
v = int(v)
|
|
69
|
+
return _drawing().Color.FromArgb(System.Int32(v - 0x100000000 if v >= 0x80000000 else v))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _value(v):
|
|
73
|
+
if isinstance(v, dict) and "argb" in v:
|
|
74
|
+
return _argb(v["argb"])
|
|
75
|
+
return v
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _enum(type_name, wanted):
|
|
79
|
+
import System
|
|
80
|
+
t = _find_type(type_name)
|
|
81
|
+
if t is None:
|
|
82
|
+
raise OpError("unavailable", "%s not found" % type_name)
|
|
83
|
+
names = list(System.Enum.GetNames(t))
|
|
84
|
+
for n in names:
|
|
85
|
+
if n == wanted:
|
|
86
|
+
return System.Enum.Parse(t, n)
|
|
87
|
+
for n in names:
|
|
88
|
+
if wanted in n:
|
|
89
|
+
return System.Enum.Parse(t, n)
|
|
90
|
+
raise OpError("bad_args", "%s has no %s (%s)" % (type_name, wanted, ", ".join(names)))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _pset(o, name, value):
|
|
94
|
+
"""Set a property, also one implemented explicitly on an interface."""
|
|
95
|
+
t = o.GetType()
|
|
96
|
+
for it in [t] + list(t.GetInterfaces()):
|
|
97
|
+
p = it.GetProperty(name)
|
|
98
|
+
if p is not None and p.CanWrite:
|
|
99
|
+
p.SetValue(o, value, None)
|
|
100
|
+
return
|
|
101
|
+
raise AttributeError(name)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class _Modify(object):
|
|
105
|
+
def __init__(self, proj, obj):
|
|
106
|
+
self.h, self.g = proj.handle, obj.guid
|
|
107
|
+
|
|
108
|
+
def __enter__(self):
|
|
109
|
+
self.mgr = _object_mgr()
|
|
110
|
+
self.mo = self.mgr.GetObjectToModify(self.h, self.g)
|
|
111
|
+
return self.mo.Object
|
|
112
|
+
|
|
113
|
+
def __exit__(self, et, ev, tb):
|
|
114
|
+
self.mgr.SetObject(self.mo, et is None, None)
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _creator(kind):
|
|
119
|
+
import System
|
|
120
|
+
from System.Reflection import BindingFlags
|
|
121
|
+
t = _find_type("_3S.CoDeSys.ScriptDriverVisualization.APEnvironment")
|
|
122
|
+
if t is None:
|
|
123
|
+
raise OpError("unavailable", "The visualization script driver isn't loaded.")
|
|
124
|
+
f = t.GetMethod("CreateVisualElemCreatorFactory", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).Invoke(None, None)
|
|
125
|
+
return f.CreateInstance(kind)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _gradient(c1, c2, angle):
|
|
129
|
+
import System
|
|
130
|
+
GV = _find_type("_3S.CoDeSys.PropertyView.GradientView")
|
|
131
|
+
k = [k for k in GV.GetConstructors() if len(k.GetParameters()) == 9][0]
|
|
132
|
+
ps = k.GetParameters()
|
|
133
|
+
con_t = ps[0].ParameterType
|
|
134
|
+
|
|
135
|
+
def con(v):
|
|
136
|
+
return System.Activator.CreateInstance(con_t, System.Array[object]([_argb(v)]))
|
|
137
|
+
look = System.Enum.GetValues(ps[4].ParameterType).GetValue(0)
|
|
138
|
+
SD = _drawing()
|
|
139
|
+
return k.Invoke(System.Array[object]([con(c1), con(c2), int(angle), SD.Point(0, 0), look, 1, 0, con(c2), None]))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _font(spec):
|
|
143
|
+
SD = _drawing()
|
|
144
|
+
name, size = spec[0], float(spec[1])
|
|
145
|
+
style = SD.FontStyle.Bold if len(spec) > 2 and spec[2] == "bold" else SD.FontStyle.Regular
|
|
146
|
+
return SD.Font(name, size, style)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
_SIMPLE = {"rect": "RECTANGLE", "round": "ROUNDRECT", "circle": "CIRCLE"}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _simple_creator(el):
|
|
153
|
+
import System
|
|
154
|
+
SD = _drawing()
|
|
155
|
+
cr = _creator("VisuFbElemSimple")
|
|
156
|
+
_pset(cr, "Type", _enum("_3S.CoDeSys.VisualElem.VisuEnumSimpleType", _SIMPLE[el["kind"]]))
|
|
157
|
+
_pset(cr, "Position", SD.Rectangle(int(el["x"]), int(el["y"]), int(el["w"]), int(el["h"])))
|
|
158
|
+
if el.get("text") is not None:
|
|
159
|
+
_pset(cr, "Text", el["text"])
|
|
160
|
+
_pset(cr, "HorizontalAlignment", _enum("_3S.CoDeSys.VisualElem.VisuEnumHorizontalAlignment", el.get("align") or "HCENTER"))
|
|
161
|
+
if el.get("font"):
|
|
162
|
+
m = cr.GetType().GetInterface("ITextElementCreator").GetMethod("SetFont")
|
|
163
|
+
m.Invoke(cr, System.Array[object]([_font(el["font"]), _argb(el.get("color", 0xFF0F172A))]))
|
|
164
|
+
if el.get("fill") is None:
|
|
165
|
+
try:
|
|
166
|
+
_pset(cr, "FillAttributes", _enum("_3S.CoDeSys.VisualElem.VisuEnumBrushStyle", "NULL"))
|
|
167
|
+
except OpError:
|
|
168
|
+
_pset(cr, "FillAttributes", _enum("_3S.CoDeSys.VisualElem.VisuEnumBrushStyle", "HOLLOW"))
|
|
169
|
+
else:
|
|
170
|
+
_pset(cr, "Color", _argb(el["fill"]))
|
|
171
|
+
if el.get("frame") is None:
|
|
172
|
+
_pset(cr, "FrameAttributes", _enum("_3S.CoDeSys.VisualElem.VisuEnumPenStyle", "HOLLOW"))
|
|
173
|
+
else:
|
|
174
|
+
_pset(cr, "FrameColor", _argb(el["frame"]))
|
|
175
|
+
_pset(cr, "LineWidth", int(el.get("lineWidth") or 1))
|
|
176
|
+
if el.get("gradient"):
|
|
177
|
+
g = el["gradient"]
|
|
178
|
+
_pset(cr, "Gradient", _gradient(g[0], g[1], g[2] if len(g) > 2 else 90))
|
|
179
|
+
_pset(cr, "UseGradient", True)
|
|
180
|
+
if el.get("invisible"):
|
|
181
|
+
_pset(cr, "InvisibleExpression", el["invisible"])
|
|
182
|
+
return cr
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _add_created(proj, visu, cr, name):
|
|
186
|
+
elem = cr.CreateVisualElement(name)
|
|
187
|
+
with _Modify(proj, visu) as vo:
|
|
188
|
+
elem.Id = vo.UniqueId
|
|
189
|
+
elem.OwningObjectGuid = vo.MetaObject.ObjectGuid
|
|
190
|
+
vo.VisualElemList.Add(elem)
|
|
191
|
+
return elem.Id
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _script_element(visu, eid):
|
|
195
|
+
for e in visu.visual_element_list:
|
|
196
|
+
if e.id == eid:
|
|
197
|
+
return e
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _find_child(c, name):
|
|
202
|
+
for ch in c.app.get_children(False):
|
|
203
|
+
if name_of(ch).lower() == name.lower():
|
|
204
|
+
return ch
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def op_visu_build(args):
|
|
209
|
+
c = Ctx()
|
|
210
|
+
if not hasattr(c.app, "create_visualobject"):
|
|
211
|
+
raise OpError("unavailable", "This CODESYS has no visualization scripting (CODESYS Visualization 4.7 or later is needed).")
|
|
212
|
+
name = args["name"]
|
|
213
|
+
for n in (name, args.get("image_pool")):
|
|
214
|
+
if not n:
|
|
215
|
+
continue
|
|
216
|
+
old = _find_child(c, n)
|
|
217
|
+
if old is not None:
|
|
218
|
+
if not args.get("replace"):
|
|
219
|
+
raise OpError("exists", "%s already exists. Pass replace to rebuild it." % n)
|
|
220
|
+
old.remove()
|
|
221
|
+
warnings = []
|
|
222
|
+
if args.get("images"):
|
|
223
|
+
pool = c.app.create_imagepool(args["image_pool"])
|
|
224
|
+
for img in args["images"]:
|
|
225
|
+
try:
|
|
226
|
+
pool.images.add(img["id"], img["path"], se.ImageLinkType.Embedded, se.AutoUpdateMode.Never)
|
|
227
|
+
except Exception as ex:
|
|
228
|
+
warnings.append("image %s: %s" % (img["id"], to_text(ex)))
|
|
229
|
+
visu = c.app.create_visualobject(name)
|
|
230
|
+
if args.get("size") or args.get("dialog"):
|
|
231
|
+
try:
|
|
232
|
+
_visual_property(c, visu, args.get("size"), bool(args.get("dialog")))
|
|
233
|
+
except Exception as ex:
|
|
234
|
+
warnings.append("size/dialog: %s" % to_text(ex))
|
|
235
|
+
T = se.VisualElementType
|
|
236
|
+
corners = []
|
|
237
|
+
for i, el in enumerate(args.get("elements") or []):
|
|
238
|
+
label = el.get("name") or "E%d" % i
|
|
239
|
+
try:
|
|
240
|
+
if el["kind"] in _SIMPLE:
|
|
241
|
+
eid = _add_created(c.proj, visu, _simple_creator(el), label)
|
|
242
|
+
e = _script_element(visu, eid)
|
|
243
|
+
else:
|
|
244
|
+
e = visu.visual_element_list.add_element({"switcher": T.ImageSwitcher, "image": T.Image}[el["kind"]])
|
|
245
|
+
for p, v in (("Position.X", el["x"]), ("Position.Y", el["y"]), ("Position.Width", el["w"]), ("Position.Height", el["h"])):
|
|
246
|
+
e.set_property(p, int(v))
|
|
247
|
+
eid = e.id
|
|
248
|
+
except Exception as ex:
|
|
249
|
+
warnings.append("%s: not added: %s" % (label, to_text(ex)))
|
|
250
|
+
continue
|
|
251
|
+
for path, v in (el.get("props") or {}).items():
|
|
252
|
+
try:
|
|
253
|
+
e.set_property(path, _value(v))
|
|
254
|
+
except Exception as ex:
|
|
255
|
+
if len(warnings) < 40:
|
|
256
|
+
warnings.append("%s %s: %s" % (label, path, to_text(ex)))
|
|
257
|
+
if el["kind"] not in _SIMPLE and (el.get("click") or el.get("goto") or el.get("dialog") or el.get("close_dialog") or el.get("login")):
|
|
258
|
+
# Image elements have no input handling: an input action there makes CODESYS's
|
|
259
|
+
# code generation raise an assertion dialog. Click areas are simple shapes.
|
|
260
|
+
warnings.append("%s: input actions only go on shapes, not on %s" % (label, el["kind"]))
|
|
261
|
+
el = dict(el)
|
|
262
|
+
for k in ("click", "goto", "dialog", "close_dialog", "login"):
|
|
263
|
+
el.pop(k, None)
|
|
264
|
+
if el.get("click"):
|
|
265
|
+
try:
|
|
266
|
+
e.add_input_action(visu.input_action_factory.create_execute_st_code(el["click"]), se.InputActionEventType.OnMouseClick)
|
|
267
|
+
except Exception as ex:
|
|
268
|
+
warnings.append("%s click: %s" % (label, to_text(ex)))
|
|
269
|
+
if el.get("goto"):
|
|
270
|
+
try:
|
|
271
|
+
act = visu.input_action_factory.create_change_shown_visualization(se.ChangeShownVisualizationType.Assignment, el["goto"])
|
|
272
|
+
e.add_input_action(act, se.InputActionEventType.OnMouseClick)
|
|
273
|
+
except Exception as ex:
|
|
274
|
+
warnings.append("%s goto: %s" % (label, to_text(ex)))
|
|
275
|
+
if el.get("dialog"):
|
|
276
|
+
try:
|
|
277
|
+
from System.Collections.Generic import Dictionary, List
|
|
278
|
+
d = el["dialog"]
|
|
279
|
+
act = visu.input_action_factory.create_open_dialog(d["name"], Dictionary[object, object](), List[object](), True, False, str(int(d.get("x", 200))), str(int(d.get("y", 120))))
|
|
280
|
+
e.add_input_action(act, se.InputActionEventType.OnMouseClick)
|
|
281
|
+
except Exception as ex:
|
|
282
|
+
warnings.append("%s dialog: %s" % (label, to_text(ex)))
|
|
283
|
+
if el.get("login"):
|
|
284
|
+
try:
|
|
285
|
+
lt = _find_type("_3S.CoDeSys.VisualElem.VisuUserMgmtInputActionFactory")
|
|
286
|
+
import System
|
|
287
|
+
act = System.Activator.CreateInstance(lt).CreateInputAction()
|
|
288
|
+
e.add_input_action(act, se.InputActionEventType.OnMouseClick)
|
|
289
|
+
except Exception as ex:
|
|
290
|
+
warnings.append("%s login: %s" % (label, to_text(ex)))
|
|
291
|
+
if el.get("close_dialog"):
|
|
292
|
+
try:
|
|
293
|
+
act = visu.input_action_factory.create_close_dialog(el["close_dialog"], "OK")
|
|
294
|
+
e.add_input_action(act, se.InputActionEventType.OnMouseClick)
|
|
295
|
+
except Exception as ex:
|
|
296
|
+
warnings.append("%s close: %s" % (label, to_text(ex)))
|
|
297
|
+
if el.get("radius"):
|
|
298
|
+
corners.append((eid, int(el["radius"])))
|
|
299
|
+
try:
|
|
300
|
+
with _Modify(c.proj, visu) as vo:
|
|
301
|
+
if args.get("background") is not None:
|
|
302
|
+
import System
|
|
303
|
+
bg = vo.VisualElemList.Background
|
|
304
|
+
bg.UseBackgroundColor = True
|
|
305
|
+
v = int(args["background"])
|
|
306
|
+
bg.BackgroundColor = System.Int32(v - 0x100000000 if v >= 0x80000000 else v)
|
|
307
|
+
for eid, r in corners:
|
|
308
|
+
el = vo.FindElementById(eid)
|
|
309
|
+
if el is not None:
|
|
310
|
+
el.GetVisualElemMember("m_Radius.m_iRadius", True, r).Value = r
|
|
311
|
+
except Exception as ex:
|
|
312
|
+
warnings.append("background/corners: %s" % to_text(ex))
|
|
313
|
+
return {"name": name_of(visu), "path": name_of(visu), "kind": "visualization", "elements": len(visu.visual_element_list), "warnings": warnings, "saved": c.save_if(args)}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _visual_property(c, visu, size, dialog):
|
|
317
|
+
"""The visualization's own settings: canvas size, and whether it is a dialog."""
|
|
318
|
+
import System
|
|
319
|
+
SD = _drawing()
|
|
320
|
+
mgr = _object_mgr()
|
|
321
|
+
mo = mgr.GetObjectToModify(c.proj.handle, visu.guid)
|
|
322
|
+
ok = False
|
|
323
|
+
try:
|
|
324
|
+
props = [p for p in mo.Properties if p.GetType().Name == "VisualProperty"]
|
|
325
|
+
if props:
|
|
326
|
+
vp = props[0]
|
|
327
|
+
if size:
|
|
328
|
+
vp.Size = SD.Size(int(size[0]), int(size[1]))
|
|
329
|
+
if dialog:
|
|
330
|
+
vp.UsageType = System.Enum.Parse(_find_type("_3S.CoDeSys.VisualObject.VisualUsageType"), "Dialog")
|
|
331
|
+
ok = True
|
|
332
|
+
finally:
|
|
333
|
+
mgr.SetObject(mo, ok, None)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def op_visu_manager(args):
|
|
337
|
+
"""Start page, client size and style of the Visualization Manager, TargetVisu and WebVisu."""
|
|
338
|
+
import System
|
|
339
|
+
c = Ctx()
|
|
340
|
+
SD = _drawing()
|
|
341
|
+
done, warnings = [], []
|
|
342
|
+
style = None
|
|
343
|
+
if args.get("style"):
|
|
344
|
+
repo = _static("_3S.CoDeSys.VisualEditor.APEnvironment", "VisualizationStylesRepository")
|
|
345
|
+
for st in (repo.GetAllStyles() if repo is not None else []):
|
|
346
|
+
nm = to_text(_get(st, "Name") or "")
|
|
347
|
+
if nm.lower() == args["style"].lower():
|
|
348
|
+
style = to_text(_get(st, "DisplayName"))
|
|
349
|
+
if style is None:
|
|
350
|
+
warnings.append("style %s is not installed" % args["style"])
|
|
351
|
+
for name in ("Visualization Manager", "TargetVisu", "WebVisu"):
|
|
352
|
+
obj = _find_child_deep(c, name)
|
|
353
|
+
if obj is None:
|
|
354
|
+
continue
|
|
355
|
+
try:
|
|
356
|
+
with _Modify(c.proj, obj) as vo:
|
|
357
|
+
vs = vo.ViewSettings
|
|
358
|
+
if args.get("start"):
|
|
359
|
+
vs.StartVisu = args["start"]
|
|
360
|
+
if name == "Visualization Manager":
|
|
361
|
+
if style:
|
|
362
|
+
vs.VisualizationStyle = style
|
|
363
|
+
elif args.get("width") and args.get("height"):
|
|
364
|
+
try:
|
|
365
|
+
vs.ClientSizeMode = System.Enum.Parse(vs.ClientSizeMode.GetType(), "Specified")
|
|
366
|
+
except Exception:
|
|
367
|
+
pass
|
|
368
|
+
vs.ClientSize = SD.Size(int(args["width"]), int(args["height"]))
|
|
369
|
+
try:
|
|
370
|
+
vs.BestFit = True
|
|
371
|
+
except Exception:
|
|
372
|
+
pass
|
|
373
|
+
done.append(name)
|
|
374
|
+
except Exception as ex:
|
|
375
|
+
warnings.append("%s: %s" % (name, to_text(ex)))
|
|
376
|
+
return {"updated": done, "warnings": warnings, "saved": c.save_if(args)}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def _get(o, name):
|
|
380
|
+
try:
|
|
381
|
+
return getattr(o, name)
|
|
382
|
+
except Exception:
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _find_child_deep(c, name):
|
|
387
|
+
for ch in c.app.get_children(True):
|
|
388
|
+
if name_of(ch).lower() == name.lower():
|
|
389
|
+
return ch
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def op_visu_render(args):
|
|
394
|
+
"""Paints a visualization into a PNG (no editor or window needed)."""
|
|
395
|
+
c = Ctx()
|
|
396
|
+
visu = _find_child(c, args["name"])
|
|
397
|
+
if visu is None:
|
|
398
|
+
raise OpError("not_found", "No visualization named %s." % args["name"])
|
|
399
|
+
mgr = _object_mgr()
|
|
400
|
+
SD = _drawing()
|
|
401
|
+
w, h = int(args.get("width") or 1280), int(args.get("height") or 720)
|
|
402
|
+
bmp = SD.Bitmap(w, h)
|
|
403
|
+
g = SD.Graphics.FromImage(bmp)
|
|
404
|
+
try:
|
|
405
|
+
g.Clear(SD.Color.White)
|
|
406
|
+
mgr.GetObjectToRead(c.proj.handle, visu.guid).Object.Paint(g, SD.Rectangle(0, 0, w, h))
|
|
407
|
+
finally:
|
|
408
|
+
g.Dispose()
|
|
409
|
+
path = args["path"]
|
|
410
|
+
folder = os.path.dirname(path)
|
|
411
|
+
if folder and not os.path.isdir(folder):
|
|
412
|
+
os.makedirs(folder)
|
|
413
|
+
bmp.Save(path, SD.Imaging.ImageFormat.Png)
|
|
414
|
+
bmp.Dispose()
|
|
415
|
+
return {"path": path}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
mcp_ops.OPS.update({
|
|
419
|
+
"visu.manager": op_visu_manager,
|
|
420
|
+
"visu.build": op_visu_build,
|
|
421
|
+
"visu.render": op_visu_render,
|
|
422
|
+
})
|
package/watcher/watcher.py
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
# (with --show-window: no --noUI and CODESYS_MCP_UI=1. The same blocking loop runs: CODESYS
|
|
6
6
|
# applies script_prompt_handling only while a script runs, so a timer that runs requests after
|
|
7
7
|
# the script returned would show every prompt as a dialog. system.delay() serves the window's
|
|
8
|
-
# message loop, so the window keeps painting while the loop waits.
|
|
8
|
+
# message loop, so the window keeps painting while the loop waits. Objects that requests
|
|
9
|
+
# write or create open in an editor tab there; see mcp_ui.py.)
|
|
9
10
|
# Environment (inherited from the server):
|
|
10
11
|
# CODESYS_MCP_DIR session folder (requests/, results/, heartbeat.json, ready.json, watcher.log)
|
|
11
12
|
# CODESYS_MCP_SCRIPTS folder holding this file and its modules
|
|
@@ -50,6 +51,24 @@ STATE = {"count": 0, "last_beat": 0.0, "busy_since": None, "op": None}
|
|
|
50
51
|
# A dialog must stay up this long before the server is told, so brief windows don't count.
|
|
51
52
|
DIALOG_GRACE_S = 10.0
|
|
52
53
|
|
|
54
|
+
# With the window shown (--show-window), these ops open the object they wrote in an editor.
|
|
55
|
+
UI = {"on": False}
|
|
56
|
+
FOLLOW_OPS = ("object.write", "object.create", "visu.build")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def follow_in_editor(ops_mod, result):
|
|
60
|
+
"""Opens the object a request wrote in its editor, so the window shows the code."""
|
|
61
|
+
path = (result or {}).get("path")
|
|
62
|
+
if not path or (result or {}).get("kind") == "folder":
|
|
63
|
+
return
|
|
64
|
+
try:
|
|
65
|
+
import scriptengine as se
|
|
66
|
+
import mcp_ui
|
|
67
|
+
obj = ops_mod.Ctx().resolve(path)
|
|
68
|
+
log("editor %s: %s" % (mcp_ui.open_editor(se, obj), path))
|
|
69
|
+
except BaseException as e:
|
|
70
|
+
log("editor not opened for %s: %s" % (path, to_text(e)))
|
|
71
|
+
|
|
53
72
|
|
|
54
73
|
def log(msg):
|
|
55
74
|
try:
|
|
@@ -208,6 +227,8 @@ def handle(ops_mod, fname):
|
|
|
208
227
|
else:
|
|
209
228
|
try:
|
|
210
229
|
res = {"id": rid, "ok": True, "result": fn(args)}
|
|
230
|
+
if UI["on"] and op in FOLLOW_OPS:
|
|
231
|
+
follow_in_editor(ops_mod, res["result"])
|
|
211
232
|
except ops_mod.OpError as e:
|
|
212
233
|
res = {"id": rid, "ok": False, "error": {"code": e.code, "message": to_text(e.message)}}
|
|
213
234
|
except BaseException as e: # IronPython: KeyboardInterrupt is not an Exception
|
|
@@ -225,6 +246,7 @@ def handle(ops_mod, fname):
|
|
|
225
246
|
|
|
226
247
|
|
|
227
248
|
def main(ui=False):
|
|
249
|
+
UI["on"] = ui
|
|
228
250
|
for d in (REQ_DIR, RES_DIR):
|
|
229
251
|
if not os.path.isdir(d):
|
|
230
252
|
os.makedirs(d)
|
|
@@ -232,6 +254,12 @@ def main(ui=False):
|
|
|
232
254
|
import scriptengine as se
|
|
233
255
|
import mcp_ops
|
|
234
256
|
import mcp_sim # noqa: F401 (adds its operations to mcp_ops.OPS)
|
|
257
|
+
import mcp_visu # noqa: F401 (adds the visualization operations)
|
|
258
|
+
import mcp_lang # noqa: F401 (adds signatures and cross-references)
|
|
259
|
+
import mcp_trace # noqa: F401 (adds trace recording; after mcp_visu)
|
|
260
|
+
import mcp_debug # noqa: F401 (adds breakpoints and stepping)
|
|
261
|
+
import mcp_devices # noqa: F401 (adds device search and add)
|
|
262
|
+
import mcp_alarms # noqa: F401 (adds alarm management and visu user groups)
|
|
235
263
|
prompts = setup_prompts(se)
|
|
236
264
|
log(prompts)
|
|
237
265
|
if not hasattr(se, "FAKE"):
|
|
@@ -277,6 +305,12 @@ def main_attach():
|
|
|
277
305
|
import scriptengine as se
|
|
278
306
|
import mcp_ops
|
|
279
307
|
import mcp_sim # noqa: F401 (adds its operations to mcp_ops.OPS)
|
|
308
|
+
import mcp_visu # noqa: F401 (adds the visualization operations)
|
|
309
|
+
import mcp_lang # noqa: F401 (adds signatures and cross-references)
|
|
310
|
+
import mcp_trace # noqa: F401 (adds trace recording; after mcp_visu)
|
|
311
|
+
import mcp_debug # noqa: F401 (adds breakpoints and stepping)
|
|
312
|
+
import mcp_devices # noqa: F401 (adds device search and add)
|
|
313
|
+
import mcp_alarms # noqa: F401 (adds alarm management and visu user groups)
|
|
280
314
|
prompts = setup_prompts(se)
|
|
281
315
|
log(prompts)
|
|
282
316
|
old = getattr(__builtin__, "_mcp_attach_timer", None)
|