@aryacode/dsh-engineering 0.0.0-stage → 0.4.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.
Files changed (284) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +230 -2
  4. package/cordis.patch.yml +123 -0
  5. package/lib/client.js +3448 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +9 -0
  8. package/locale/en.json +6 -0
  9. package/locale/zh.json +6 -0
  10. package/package.json +94 -3
  11. package/vendor/ftview/README.md +55 -0
  12. package/vendor/ftview/SKILL.md +74 -0
  13. package/vendor/ftview/lib/checks.d.ts +27 -0
  14. package/vendor/ftview/lib/checks.js +39 -0
  15. package/vendor/ftview/lib/core.d.ts +208 -0
  16. package/vendor/ftview/lib/core.js +160 -0
  17. package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
  18. package/vendor/ftview/lib/edit/drafts.js +84 -0
  19. package/vendor/ftview/lib/edit/operations.d.ts +62 -0
  20. package/vendor/ftview/lib/edit/operations.js +126 -0
  21. package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
  22. package/vendor/ftview/lib/edit/prepare.js +133 -0
  23. package/vendor/ftview/lib/edit/tools.d.ts +25 -0
  24. package/vendor/ftview/lib/edit/tools.js +198 -0
  25. package/vendor/ftview/lib/edit.d.ts +30 -0
  26. package/vendor/ftview/lib/edit.js +38 -0
  27. package/vendor/ftview/lib/index.d.ts +31 -0
  28. package/vendor/ftview/lib/index.js +36 -0
  29. package/vendor/ftview/lib/model.d.ts +80 -0
  30. package/vendor/ftview/lib/model.js +267 -0
  31. package/vendor/ftview/lib/session.d.ts +48 -0
  32. package/vendor/ftview/lib/session.js +98 -0
  33. package/vendor/ftview/lib/skill.d.ts +29 -0
  34. package/vendor/ftview/lib/skill.js +43 -0
  35. package/vendor/ftview/lib/studio/discover.d.ts +36 -0
  36. package/vendor/ftview/lib/studio/discover.js +87 -0
  37. package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
  38. package/vendor/ftview/lib/studio/preflight.js +64 -0
  39. package/vendor/ftview/lib/studio/tools.d.ts +15 -0
  40. package/vendor/ftview/lib/studio/tools.js +83 -0
  41. package/vendor/ftview/lib/studio.d.ts +30 -0
  42. package/vendor/ftview/lib/studio.js +37 -0
  43. package/vendor/ftview/lib/tools.d.ts +14 -0
  44. package/vendor/ftview/lib/tools.js +202 -0
  45. package/vendor/l5x/README.md +62 -0
  46. package/vendor/l5x/SKILL.md +81 -0
  47. package/vendor/l5x/lib/approvals.d.ts +50 -0
  48. package/vendor/l5x/lib/approvals.js +93 -0
  49. package/vendor/l5x/lib/client.js +412 -0
  50. package/vendor/l5x/lib/client.js.map +1 -0
  51. package/vendor/l5x/lib/core/errors.d.ts +38 -0
  52. package/vendor/l5x/lib/core/errors.js +48 -0
  53. package/vendor/l5x/lib/core/params.d.ts +49 -0
  54. package/vendor/l5x/lib/core/params.js +39 -0
  55. package/vendor/l5x/lib/core/result.d.ts +46 -0
  56. package/vendor/l5x/lib/core/result.js +21 -0
  57. package/vendor/l5x/lib/core/tool.d.ts +102 -0
  58. package/vendor/l5x/lib/core/tool.js +19 -0
  59. package/vendor/l5x/lib/drafts.d.ts +66 -0
  60. package/vendor/l5x/lib/drafts.js +98 -0
  61. package/vendor/l5x/lib/editing.d.ts +30 -0
  62. package/vendor/l5x/lib/editing.js +42 -0
  63. package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
  64. package/vendor/l5x/lib/engine/acd-edit.js +115 -0
  65. package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
  66. package/vendor/l5x/lib/engine/analysis.js +258 -0
  67. package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
  68. package/vendor/l5x/lib/engine/capabilities.js +40 -0
  69. package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
  70. package/vendor/l5x/lib/engine/change-set.js +446 -0
  71. package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
  72. package/vendor/l5x/lib/engine/configuration.js +42 -0
  73. package/vendor/l5x/lib/engine/details.d.ts +47 -0
  74. package/vendor/l5x/lib/engine/details.js +173 -0
  75. package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
  76. package/vendor/l5x/lib/engine/direct-polling.js +193 -0
  77. package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
  78. package/vendor/l5x/lib/engine/download-review.js +63 -0
  79. package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
  80. package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
  81. package/vendor/l5x/lib/engine/observations.d.ts +41 -0
  82. package/vendor/l5x/lib/engine/observations.js +67 -0
  83. package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
  84. package/vendor/l5x/lib/engine/project-analysis.js +314 -0
  85. package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
  86. package/vendor/l5x/lib/engine/review-evidence.js +49 -0
  87. package/vendor/l5x/lib/engine/review.d.ts +18 -0
  88. package/vendor/l5x/lib/engine/review.js +123 -0
  89. package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
  90. package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
  91. package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
  92. package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
  93. package/vendor/l5x/lib/engine/usage.d.ts +16 -0
  94. package/vendor/l5x/lib/engine/usage.js +92 -0
  95. package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
  96. package/vendor/l5x/lib/engine/xml-regions.js +77 -0
  97. package/vendor/l5x/lib/fs.d.ts +17 -0
  98. package/vendor/l5x/lib/fs.js +36 -0
  99. package/vendor/l5x/lib/index.d.ts +36 -0
  100. package/vendor/l5x/lib/index.js +42 -0
  101. package/vendor/l5x/lib/live.d.ts +48 -0
  102. package/vendor/l5x/lib/live.js +48 -0
  103. package/vendor/l5x/lib/model/aois.d.ts +39 -0
  104. package/vendor/l5x/lib/model/aois.js +112 -0
  105. package/vendor/l5x/lib/model/errors.d.ts +15 -0
  106. package/vendor/l5x/lib/model/errors.js +18 -0
  107. package/vendor/l5x/lib/model/index.d.ts +20 -0
  108. package/vendor/l5x/lib/model/index.js +19 -0
  109. package/vendor/l5x/lib/model/module.d.ts +37 -0
  110. package/vendor/l5x/lib/model/module.js +54 -0
  111. package/vendor/l5x/lib/model/modules.d.ts +24 -0
  112. package/vendor/l5x/lib/model/modules.js +55 -0
  113. package/vendor/l5x/lib/model/parse.d.ts +32 -0
  114. package/vendor/l5x/lib/model/parse.js +116 -0
  115. package/vendor/l5x/lib/model/project.d.ts +15 -0
  116. package/vendor/l5x/lib/model/project.js +31 -0
  117. package/vendor/l5x/lib/model/routines.d.ts +71 -0
  118. package/vendor/l5x/lib/model/routines.js +183 -0
  119. package/vendor/l5x/lib/model/tags.d.ts +51 -0
  120. package/vendor/l5x/lib/model/tags.js +119 -0
  121. package/vendor/l5x/lib/model/types.d.ts +304 -0
  122. package/vendor/l5x/lib/model/types.js +12 -0
  123. package/vendor/l5x/lib/model/udts.d.ts +34 -0
  124. package/vendor/l5x/lib/model/udts.js +81 -0
  125. package/vendor/l5x/lib/model/xml.d.ts +139 -0
  126. package/vendor/l5x/lib/model/xml.js +291 -0
  127. package/vendor/l5x/lib/model/xref.d.ts +28 -0
  128. package/vendor/l5x/lib/model/xref.js +154 -0
  129. package/vendor/l5x/lib/session.d.ts +119 -0
  130. package/vendor/l5x/lib/session.js +236 -0
  131. package/vendor/l5x/lib/skill.d.ts +30 -0
  132. package/vendor/l5x/lib/skill.js +44 -0
  133. package/vendor/l5x/lib/studio-state.d.ts +77 -0
  134. package/vendor/l5x/lib/studio-state.js +79 -0
  135. package/vendor/l5x/lib/studio.d.ts +29 -0
  136. package/vendor/l5x/lib/studio.js +40 -0
  137. package/vendor/l5x/lib/tools/checks.d.ts +19 -0
  138. package/vendor/l5x/lib/tools/checks.js +140 -0
  139. package/vendor/l5x/lib/tools/common.d.ts +41 -0
  140. package/vendor/l5x/lib/tools/common.js +84 -0
  141. package/vendor/l5x/lib/tools/controller.d.ts +21 -0
  142. package/vendor/l5x/lib/tools/controller.js +170 -0
  143. package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
  144. package/vendor/l5x/lib/tools/drafts.js +270 -0
  145. package/vendor/l5x/lib/tools/live.d.ts +21 -0
  146. package/vendor/l5x/lib/tools/live.js +175 -0
  147. package/vendor/l5x/lib/tools/read.d.ts +19 -0
  148. package/vendor/l5x/lib/tools/read.js +245 -0
  149. package/vendor/l5x/lib/tools/references.d.ts +20 -0
  150. package/vendor/l5x/lib/tools/references.js +269 -0
  151. package/vendor/l5x/lib/tools/studio.d.ts +21 -0
  152. package/vendor/l5x/lib/tools/studio.js +193 -0
  153. package/vendor/l5x/poll-worker.mjs +77 -0
  154. package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
  155. package/vendor/l5x/studio-bridge/Program.cs +304 -0
  156. package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
  157. package/vendor/mwi/README.md +60 -0
  158. package/vendor/mwi/SKILL.md +87 -0
  159. package/vendor/mwi/bridge/README.md +8 -0
  160. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
  161. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
  162. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
  163. package/vendor/mwi/bridge/start_bridge.cmd +21 -0
  164. package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
  165. package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
  166. package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
  167. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  168. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  169. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  170. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  171. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  172. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  173. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  174. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  175. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  176. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  177. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  178. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  179. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  180. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  181. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  182. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  183. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  184. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  185. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  186. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  187. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  188. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
  189. package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
  190. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
  191. package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
  192. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
  193. package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
  194. package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
  195. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
  196. package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
  197. package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
  198. package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
  199. package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
  200. package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
  201. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
  202. package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
  203. package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
  204. package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
  205. package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
  206. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
  207. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
  208. package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
  209. package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
  210. package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
  211. package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
  212. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
  213. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
  214. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
  215. package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
  216. package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
  217. package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
  218. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
  219. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
  220. package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
  221. package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
  222. package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
  223. package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
  224. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
  225. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
  226. package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
  227. package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
  228. package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
  229. package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
  230. package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
  231. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
  232. package/vendor/mwi/code/mw_code.py +1258 -0
  233. package/vendor/mwi/legacy/edit-session.js +50 -0
  234. package/vendor/mwi/legacy/fb-insertion.js +74 -0
  235. package/vendor/mwi/legacy/native-code.js +83 -0
  236. package/vendor/mwi/legacy/native-groups.js +75 -0
  237. package/vendor/mwi/legacy/native-structure.js +143 -0
  238. package/vendor/mwi/legacy/native-variables.js +77 -0
  239. package/vendor/mwi/legacy/pou-conversion.js +47 -0
  240. package/vendor/mwi/legacy/pou-package.js +66 -0
  241. package/vendor/mwi/lib/core/bridge.d.ts +71 -0
  242. package/vendor/mwi/lib/core/bridge.js +53 -0
  243. package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
  244. package/vendor/mwi/lib/core/code-engine.js +109 -0
  245. package/vendor/mwi/lib/core/errors.d.ts +37 -0
  246. package/vendor/mwi/lib/core/errors.js +47 -0
  247. package/vendor/mwi/lib/core/params.d.ts +52 -0
  248. package/vendor/mwi/lib/core/params.js +46 -0
  249. package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
  250. package/vendor/mwi/lib/core/ps-bridge.js +127 -0
  251. package/vendor/mwi/lib/core/result.d.ts +45 -0
  252. package/vendor/mwi/lib/core/result.js +20 -0
  253. package/vendor/mwi/lib/core/tool.d.ts +80 -0
  254. package/vendor/mwi/lib/core/tool.js +55 -0
  255. package/vendor/mwi/lib/edit/compare.d.ts +33 -0
  256. package/vendor/mwi/lib/edit/compare.js +70 -0
  257. package/vendor/mwi/lib/edit.d.ts +20 -0
  258. package/vendor/mwi/lib/edit.js +32 -0
  259. package/vendor/mwi/lib/index.d.ts +21 -0
  260. package/vendor/mwi/lib/index.js +33 -0
  261. package/vendor/mwi/lib/read/service.d.ts +117 -0
  262. package/vendor/mwi/lib/read/service.js +229 -0
  263. package/vendor/mwi/lib/read.d.ts +21 -0
  264. package/vendor/mwi/lib/read.js +33 -0
  265. package/vendor/mwi/lib/session/project.d.ts +34 -0
  266. package/vendor/mwi/lib/session/project.js +54 -0
  267. package/vendor/mwi/lib/session/service.d.ts +167 -0
  268. package/vendor/mwi/lib/session/service.js +566 -0
  269. package/vendor/mwi/lib/session/stage.d.ts +84 -0
  270. package/vendor/mwi/lib/session/stage.js +187 -0
  271. package/vendor/mwi/lib/session/workspace.d.ts +52 -0
  272. package/vendor/mwi/lib/session/workspace.js +90 -0
  273. package/vendor/mwi/lib/skill.d.ts +36 -0
  274. package/vendor/mwi/lib/skill.js +56 -0
  275. package/vendor/mwi/lib/tools/edit.d.ts +20 -0
  276. package/vendor/mwi/lib/tools/edit.js +286 -0
  277. package/vendor/mwi/lib/tools/read.d.ts +19 -0
  278. package/vendor/mwi/lib/tools/read.js +161 -0
  279. package/vendor/mwi/lib/tools/session.d.ts +20 -0
  280. package/vendor/mwi/lib/tools/session.js +166 -0
  281. package/vendor/mwi/lib/tools/verify.d.ts +19 -0
  282. package/vendor/mwi/lib/tools/verify.js +85 -0
  283. package/vendor/mwi/lib/verify.d.ts +17 -0
  284. package/vendor/mwi/lib/verify.js +29 -0
@@ -0,0 +1,268 @@
1
+ """Drive MotionWorks IEC directly through COM with pywin32.
2
+
3
+ This is the Tier 3 automation path in Python, as the project requires: the
4
+ ``Ade.Application.550`` automation server, its type library, and a **32-bit**
5
+ interpreter. It is deliberately one of two implementations of the same job --
6
+ ``mw_build.ps1`` under 32-bit PowerShell is the other -- so the two can be compared
7
+ when something goes wrong, rather than there being a single opaque path.
8
+
9
+ Why 32-bit is mandatory
10
+ -----------------------
11
+ ``mwt.exe`` is a 32-bit local COM server, so a 64-bit process cannot instantiate it.
12
+ Run this from the ``.venv32`` interpreter. ``available()`` reports the reason rather
13
+ than failing obscurely, because "pywin32 is missing" and "the IDE is not running" are
14
+ very different problems.
15
+
16
+ Why the IDE must already be running
17
+ -----------------------------------
18
+ ``Ade.Application.550`` is registered as a *LocalServer32*, so instantiating it
19
+ **launches** ``mwt.exe`` if it is not already up. A freshly launched IDE has no
20
+ project services ready, and calling into it returns ``Internal error`` for every
21
+ project call. So this client never launches the IDE; it refuses and says so.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import sys
27
+ import time
28
+ from pathlib import Path
29
+
30
+ #: Repository root, two levels up from this module.
31
+ PROJECT_ROOT = Path(__file__).resolve().parents[2]
32
+
33
+ #: The automation ProgID, and its CLSID for error messages.
34
+ PROG_ID = "Ade.Application.550"
35
+ CLSID = "{05500000-0108-2706-100F-0080C7AC3298}"
36
+
37
+ #: ``AdeCompileType`` values. 1 is an asynchronous Build; 2 is Rebuild. 0 and 3-6 are
38
+ #: not valid -- confirmed by probing the server, which rejects them.
39
+ COMPILE_BUILD = 1
40
+ COMPILE_REBUILD = 2
41
+
42
+
43
+ def interpreter_32() -> Path | None:
44
+ """The 32-bit interpreter this server delegates COM work to."""
45
+ candidate = PROJECT_ROOT / ".venv32" / "Scripts" / "python.exe"
46
+ return candidate if candidate.is_file() else None
47
+
48
+
49
+ def query_via_32bit(compile_kind: int = 0, timeout: float = 120.0) -> dict[str, object]:
50
+ """Ask the 32-bit interpreter to query the automation server, and return its JSON.
51
+
52
+ This MCP server runs under a 64-bit interpreter, which cannot instantiate the
53
+ 32-bit COM server. Only ``mw_com_status`` needs COM -- everything else works on
54
+ either -- so rather than requiring the whole server to run 32-bit (which would
55
+ also need the ``mcp`` package installed there), the COM work is delegated.
56
+
57
+ The child writes JSON to a file rather than stdout, because capturing a child's
58
+ output through a pipe is unreliable in this environment.
59
+ """
60
+ import json
61
+ import subprocess
62
+ import tempfile
63
+
64
+ interpreter = interpreter_32()
65
+ if interpreter is None:
66
+ return {
67
+ "ok": False,
68
+ "error": (
69
+ "no 32-bit interpreter found at .venv32\\Scripts\\python.exe. COM "
70
+ "automation needs one, because mwt.exe is a 32-bit local server."
71
+ ),
72
+ }
73
+
74
+ helper = PROJECT_ROOT / "tools" / "com_query.py"
75
+ if not helper.is_file():
76
+ return {"ok": False, "error": f"helper not found: {helper}"}
77
+
78
+ with tempfile.TemporaryDirectory() as tmp:
79
+ result = Path(tmp) / "com.json"
80
+ command = [str(interpreter), "-X", "utf8", str(helper), "--out", str(result)]
81
+ if compile_kind:
82
+ command += ["--compile", str(compile_kind)]
83
+ try:
84
+ subprocess.run(
85
+ command,
86
+ stdout=subprocess.DEVNULL,
87
+ stderr=subprocess.DEVNULL,
88
+ timeout=timeout,
89
+ creationflags=0x08000000,
90
+ )
91
+ except Exception as exc: # noqa: BLE001
92
+ return {"ok": False, "error": f"could not run the 32-bit helper: {exc}"}
93
+ try:
94
+ return json.loads(result.read_text(encoding="utf-8"))
95
+ except (OSError, json.JSONDecodeError) as exc:
96
+ return {"ok": False, "error": f"the helper produced no usable result: {exc}"}
97
+
98
+
99
+ class ComUnavailable(RuntimeError):
100
+ """The automation server could not be reached, with the reason why."""
101
+
102
+
103
+ def is_32bit() -> bool:
104
+ """Whether this interpreter is 32-bit, which the COM server requires."""
105
+ return sys.maxsize <= 2**32
106
+
107
+
108
+ def available() -> tuple[bool, str]:
109
+ """Whether this interpreter can use the automation server at all."""
110
+ if sys.platform != "win32":
111
+ return False, "not Windows"
112
+ if not is_32bit():
113
+ return (
114
+ False,
115
+ f"this is a {struct_calcsize() * 8}-bit interpreter, but mwt.exe is a "
116
+ f"32-bit local COM server; use the 32-bit interpreter (python.exe under "
117
+ f".venv32)",
118
+ )
119
+ try:
120
+ import win32com.client # noqa: F401
121
+ except ImportError as exc:
122
+ return False, (
123
+ f"pywin32 is not installed in this interpreter ({exc}); install it with: "
124
+ f"python -m pip install pywin32"
125
+ )
126
+ return True, "ok"
127
+
128
+
129
+ def struct_calcsize() -> int:
130
+ """``8`` for 64-bit, ``4`` for 32-bit, for messages."""
131
+ import struct
132
+
133
+ return struct.calcsize("P")
134
+
135
+
136
+ def connect(visible: bool = True):
137
+ """Attach to a **running** MotionWorks IEC automation server.
138
+
139
+ Raises :class:`ComUnavailable` rather than launching the IDE, because a
140
+ launched-but-uninitialised IDE answers every project call with ``Internal
141
+ error`` and that is indistinguishable from a real failure.
142
+ """
143
+ ok, reason = available()
144
+ if not ok:
145
+ raise ComUnavailable(reason)
146
+
147
+ import win32com.client
148
+
149
+ from . import ide
150
+
151
+ running = [
152
+ (pid, name)
153
+ for pid, name in ide.motionworks_running(include_helpers=False)
154
+ ]
155
+ if not running:
156
+ raise ComUnavailable(
157
+ "MotionWorks IEC is not running. Instantiating "
158
+ f"{PROG_ID} would launch it, and a freshly launched IDE has no project "
159
+ "services, so every call would fail with 'Internal error'. Start "
160
+ "MotionWorks IEC, open the project, then retry."
161
+ )
162
+
163
+ try:
164
+ app = win32com.client.GetActiveObject(PROG_ID)
165
+ except Exception as exc: # noqa: BLE001 - COM raises a variety of types
166
+ # Deliberately **no Dispatch fallback here.** Dispatch on a LocalServer32
167
+ # would launch another mwt.exe, and we have just confirmed one is already
168
+ # running; spawning a second IDE to talk to the first is never right.
169
+ raise ComUnavailable(
170
+ f"MotionWorks IEC is running (pid "
171
+ f"{', '.join(str(p) for p, _ in running)}) but its automation server "
172
+ f"could not be attached to: {exc}. This usually means the IDE is up as a "
173
+ f"COM/DDE server without having initialised its project services -- see "
174
+ f"docs/tier3-com-build.md."
175
+ ) from exc
176
+
177
+ if visible:
178
+ try:
179
+ app.Visible = True
180
+ except Exception: # noqa: BLE001 - not fatal
181
+ pass
182
+ return app
183
+
184
+
185
+ def project_state(app) -> dict[str, object]:
186
+ """Read ``IsProjectOpen`` / ``IsCompiled`` / ``IsModified`` without raising.
187
+
188
+ Each is reported as a value or as an error string. A failed call here is the
189
+ signal that the IDE is up but its project services are not -- which must not be
190
+ confused with "the project does not compile".
191
+ """
192
+ state: dict[str, object] = {}
193
+ for label, getter in (
194
+ ("project_open", lambda: app.IsProjectOpen()),
195
+ ("compiled", lambda: app.ActiveProject.IsCompiled),
196
+ ("modified", lambda: app.ActiveProject.IsModified),
197
+ ("name", lambda: app.ActiveProject.Name),
198
+ ):
199
+ try:
200
+ state[label] = getter()
201
+ except Exception as exc: # noqa: BLE001
202
+ state[label] = f"error: {exc}"
203
+ return state
204
+
205
+
206
+ def services_ready(app) -> tuple[bool, str]:
207
+ """Whether the IDE's project services are usable.
208
+
209
+ Distinguishes the important case: a ``False`` here means no compile was
210
+ attempted, so "compiled is False" would be misleading if reported as a build
211
+ failure.
212
+ """
213
+ state = project_state(app)
214
+ opened = state.get("project_open")
215
+ if isinstance(opened, str):
216
+ return False, (
217
+ "the IDE is running but its project services are not answering "
218
+ f"({opened}). No compile was attempted, so this is NOT a build failure."
219
+ )
220
+ if opened is not True:
221
+ return False, "no project is open in MotionWorks IEC"
222
+ return True, "ok"
223
+
224
+
225
+ def compile_project(
226
+ app, kind: int = COMPILE_REBUILD, settle: float = 3.0
227
+ ) -> dict[str, object]:
228
+ """Ask the IDE to Build or Rebuild the active project.
229
+
230
+ ``Ade.Compile`` rejects a second call while the compiler is running
231
+ (``"...compiler is running"``), so a rebuild is retried until it is accepted.
232
+ The result reports whether the call was accepted, which is not the same as
233
+ whether the project compiled -- read ``ActiveProject.IsCompiled`` afterwards.
234
+ """
235
+ result: dict[str, object] = {"requested": kind, "accepted": False, "note": ""}
236
+ try:
237
+ app.ActiveProject.Compile(kind)
238
+ result["accepted"] = True
239
+ except Exception as exc: # noqa: BLE001
240
+ message = str(exc)
241
+ if "compiler is running" in message.casefold():
242
+ result["note"] = "the compiler was busy; retry once it finishes"
243
+ else:
244
+ result["note"] = message
245
+
246
+ time.sleep(settle)
247
+ result["state"] = project_state(app)
248
+ return result
249
+
250
+
251
+ def summary(app) -> str:
252
+ """A one-block human-readable report, for tool output."""
253
+ ok, reason = available()
254
+ lines = [f"pywin32 COM client: {'usable' if ok else 'unusable'} ({reason})"]
255
+ if not ok:
256
+ return "\n".join(lines)
257
+
258
+ try:
259
+ state = project_state(app)
260
+ except ComUnavailable as exc:
261
+ lines.append(f"not connected: {exc}")
262
+ return "\n".join(lines)
263
+
264
+ for key in ("name", "project_open", "compiled", "modified"):
265
+ lines.append(f" {key:13}: {state.get(key)}")
266
+ ready, why = services_ready(app)
267
+ lines.append(f" services : {'ready' if ready else why}")
268
+ return "\n".join(lines)
@@ -0,0 +1,204 @@
1
+ """Read a MotionWorks project's user-defined data types from ``DT/Tyllist.typ``.
2
+
3
+ Why this exists: a POU in this project declares ``CamData : CamSegmentStruct`` and
4
+ ``CamTable : Y_MS_CAM_STRUCT``, and writing Structured Text that touches either one needs
5
+ the member names. Nothing in this plugin could read them, so the agent was writing code
6
+ against types it could not see.
7
+
8
+ The file is line-oriented, tab-separated. Measured from a real project:
9
+
10
+ (*
11
+ NDTE: 297 number of data types
12
+ NCPE: 943 number of components (members, array bounds, and so on)
13
+ NDME: 147 number of direct members
14
+ *)
15
+ 24 0<TAB>ControllerInfoTypes\\Cont<TAB>FIRMWARE_INFO<TAB>1031<TAB>5<TAB>USER<TAB>STRUCT
16
+ 25 0<TAB><TAB>VersionNumber<TAB>VERSION_NUMBER<TAB>1027<TAB>0
17
+
18
+ Splitting on tabs, a TYPE line and a MEMBER line differ in ONE place that matters:
19
+
20
+ TYPE ['24 0', 'ControllerInfoTypes\\Cont', 'FIRMWARE_INFO', '1031', '5', 'USER', 'STRUCT']
21
+ MEMBER ['25 0', '', 'VersionNumber', 'VERSION_NUMBER', '1027', '0']
22
+
23
+ so the discriminator is the CONTAINER field at index 1 - empty for a member, a namespace
24
+ path for a type - and the NAME is at index 2 in both cases. An earlier version of this
25
+ reader looked at index 2 for the container and index 3 for the name, which turned every
26
+ member into a type with an id for a name: it reported 1054 types where the header says 297.
27
+
28
+ An ARRAY carries its element type on the type line and its bound on the line after:
29
+
30
+ 15 0<TAB>ControllerInfoTypes\\Cont<TAB>BYTE32<TAB>1024<TAB>1<TAB>USER<TAB>ARRAY<TAB>BYTE<TAB>17
31
+ 15 0<TAB><TAB><TAB>0<TAB>31
32
+
33
+ Library types are NOT in this file. ``CamGenerator`` and ``Y_CamStructSelect`` are used by
34
+ the POUs and absent here, because they come from an installed library rather than the
35
+ project. This reader reports what the project defines and says so when asked for one it does
36
+ not have.
37
+ """
38
+ from __future__ import annotations
39
+
40
+ import re
41
+ from dataclasses import dataclass, field
42
+ from pathlib import Path
43
+
44
+ from .errors import NotFound
45
+
46
+ TYPE_FILE = "DT/Tyllist.typ"
47
+ _COUNTS = re.compile(r"^\s*(\w+)\s*:\s*(\d+)\s*$")
48
+ _INDEX = re.compile(r"^\s*\d+(\s+\d+)?\s*$")
49
+
50
+
51
+ @dataclass
52
+ class DataTypeMember:
53
+ """One member of a user-defined type."""
54
+
55
+ name: str
56
+ type_name: str
57
+ type_id: int = 0
58
+ array_size: int | None = None
59
+
60
+
61
+ @dataclass
62
+ class DataType:
63
+ """One user-defined type."""
64
+
65
+ name: str
66
+ kind: str = ""
67
+ type_id: int = 0
68
+ container: str = ""
69
+ declared_members: int = 0
70
+ element_type: str = ""
71
+ element_id: int = 0
72
+ members: list[DataTypeMember] = field(default_factory=list)
73
+
74
+ @property
75
+ def is_struct(self) -> bool:
76
+ return self.kind.upper() in ("STRUCT", "UNION")
77
+
78
+ @property
79
+ def is_array(self) -> bool:
80
+ return self.kind.upper() in ("ARRAY", "MATRIX")
81
+
82
+
83
+ def types_path(project_root: Path) -> Path:
84
+ candidate = Path(project_root) / TYPE_FILE
85
+ if not candidate.is_file():
86
+ raise NotFound(
87
+ f"no {TYPE_FILE} in {project_root}; this project defines no data types of its own "
88
+ f"- library types such as CamGenerator live outside the project"
89
+ )
90
+ return candidate
91
+
92
+
93
+ def _int_at(fields: list[str], index: int, default: int = 0) -> int:
94
+ if index >= len(fields):
95
+ return default
96
+ try:
97
+ return int(fields[index].strip())
98
+ except ValueError:
99
+ return default
100
+
101
+
102
+ def read_types(project_root: Path) -> tuple[list[DataType], dict[str, int]]:
103
+ """Parse the type list, returning ``(types, header_counts)``.
104
+
105
+ ``header_counts`` is the file's own ``NDTE``/``NCPE``/``NDME`` tally, which is a useful
106
+ check on the parse: a count that does not match the number of types found means the
107
+ layout is not what this reader assumes.
108
+ """
109
+ text = types_path(project_root).read_text(encoding="latin1", errors="replace")
110
+
111
+ counts: dict[str, int] = {}
112
+ types: list[DataType] = []
113
+ current: DataType | None = None
114
+ pending_array: DataType | None = None
115
+
116
+ for raw in text.splitlines():
117
+ if not raw.strip():
118
+ continue
119
+ stripped = raw.strip()
120
+ if stripped.startswith("(*") or stripped.startswith("*)"):
121
+ continue
122
+ match = _COUNTS.match(stripped)
123
+ if match:
124
+ counts[match.group(1)] = int(match.group(2))
125
+ continue
126
+
127
+ fields = raw.split("\t")
128
+ if len(fields) < 2 or not _INDEX.match(fields[0]):
129
+ continue
130
+
131
+ container, name = fields[1].strip(), (fields[2].strip() if len(fields) > 2 else "")
132
+
133
+ if container:
134
+ # A TYPE line: container, NAME, id, declared count, source, KIND, ...
135
+ if not name:
136
+ continue
137
+ kind = fields[6].strip() if len(fields) > 6 else ""
138
+ current = DataType(
139
+ name=name,
140
+ kind=kind,
141
+ type_id=_int_at(fields, 3),
142
+ container=container,
143
+ declared_members=_int_at(fields, 4),
144
+ )
145
+ if current.is_array and len(fields) > 7:
146
+ current.element_type = fields[7].strip()
147
+ current.element_id = _int_at(fields, 8)
148
+ pending_array = current
149
+ else:
150
+ pending_array = None
151
+ types.append(current)
152
+ continue
153
+
154
+ # A MEMBER line, or the bound line that follows an ARRAY type line.
155
+ if pending_array is not None and not name:
156
+ for value in fields[2:]:
157
+ if value.strip().isdigit():
158
+ pending_array.declared_members = int(value.strip())
159
+ break
160
+ pending_array = None
161
+ continue
162
+
163
+ if current is None:
164
+ continue
165
+ # The bound line of an array whose element field was blank arrives here as a
166
+ # nameless record with a number; treat that as the bound, not a member.
167
+ if not name:
168
+ for value in fields[2:]:
169
+ if value.strip().isdigit():
170
+ current.declared_members = int(value.strip())
171
+ break
172
+ continue
173
+
174
+ bound = None
175
+ for value in fields[5:]:
176
+ text_value = value.strip()
177
+ if text_value.isdigit() and int(text_value) > 1:
178
+ bound = int(text_value)
179
+ break
180
+ current.members.append(
181
+ DataTypeMember(
182
+ name=name,
183
+ type_name=fields[3].strip() if len(fields) > 3 else "",
184
+ type_id=_int_at(fields, 4),
185
+ array_size=bound,
186
+ )
187
+ )
188
+
189
+ return types, counts
190
+
191
+
192
+ def find_type(project_root: Path, name: str) -> DataType:
193
+ """One type by name, case-insensitively."""
194
+ types, _ = read_types(project_root)
195
+ for t in types:
196
+ if t.name.lower() == name.lower():
197
+ return t
198
+ defined = sorted({t.name for t in types})
199
+ sample = ", ".join(defined[:25])
200
+ raise NotFound(
201
+ f"no data type named {name!r} defined in this project. It defines {len(types)}: "
202
+ f"{sample}{' ...' if len(defined) > 25 else ''}. Types used by POUs but absent here "
203
+ f"come from an installed library rather than the project."
204
+ )