@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.
- package/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/cordis.patch.yml +123 -0
- package/lib/client.js +3448 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +94 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- 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
|
+
)
|