@codesys/mcp-toolkit 1.1.5 → 1.1.7

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 (34) hide show
  1. package/CHANGELOG.md +10 -5
  2. package/dist/bin.js +2 -41
  3. package/dist/scriptLoader.js +18 -1
  4. package/dist/scripts/check_status.py +21 -0
  5. package/dist/scripts/compile_project.py +65 -0
  6. package/dist/scripts/create_method.py +80 -0
  7. package/dist/scripts/create_pou.py +96 -0
  8. package/dist/scripts/create_project.py +54 -0
  9. package/dist/scripts/create_property.py +78 -0
  10. package/dist/scripts/ensure_project_open.py +154 -0
  11. package/dist/scripts/find_object_by_path.py +111 -0
  12. package/dist/scripts/get_pou_code.py +91 -0
  13. package/dist/scripts/get_project_structure.py +72 -0
  14. package/dist/scripts/open_project.py +25 -0
  15. package/dist/scripts/save_project.py +29 -0
  16. package/dist/scripts/set_pou_code.py +104 -0
  17. package/dist/server.js +319 -503
  18. package/package.json +16 -4
  19. package/src/scripts/check_status.py +21 -0
  20. package/src/scripts/compile_project.py +65 -0
  21. package/src/scripts/create_method.py +80 -0
  22. package/src/scripts/create_pou.py +96 -0
  23. package/src/scripts/create_project.py +54 -0
  24. package/src/scripts/create_property.py +78 -0
  25. package/src/scripts/ensure_project_open.py +154 -0
  26. package/src/scripts/find_object_by_path.py +111 -0
  27. package/src/scripts/get_pou_code.py +91 -0
  28. package/src/scripts/get_project_structure.py +72 -0
  29. package/src/scripts/open_project.py +25 -0
  30. package/src/scripts/save_project.py +29 -0
  31. package/src/scripts/set_pou_code.py +104 -0
  32. package/.claude/settings.local.json +0 -13
  33. package/CONTRIBUTING.md +0 -72
  34. package/codesys_scripts/listener_script.py +0 -1539
package/CHANGELOG.md CHANGED
@@ -5,14 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.1.5] - 2025-05-06
8
+ ## [1.1.7] - 2025-05-06
9
9
 
10
10
  ### Fixed
11
- - Bug that prevented creating POUs in new projects due to path resolution issues
12
- - Enhanced path resolution to better handle "Application" paths in both new and existing projects
13
- - Fixed TypeScript build error by updating McpServer method from start() to connect()
11
+ - Fixed Python scripts not being included in the npm package
12
+ - Enhanced script loading to search for scripts in multiple possible locations
13
+ - Added build step to copy scripts to dist folder
14
14
 
15
- ## [1.1.4] - 2025-04-30
15
+ ## [1.1.6] - 2025-05-06
16
+
17
+ ### Fixed
18
+ - Added TypeScript as dev dependency to fix build process
19
+
20
+ ## [1.1.4] - 2025-05-01
16
21
 
17
22
  ### Changed
18
23
  - Refactored the code for better maintainability
package/dist/bin.js CHANGED
@@ -8,45 +8,11 @@
8
8
  * This is the entry point for the MCP toolkit when invoked as a command-line tool.
9
9
  * It handles argument parsing and passes configuration to server.ts via function parameters.
10
10
  */
11
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- var desc = Object.getOwnPropertyDescriptor(m, k);
14
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
- desc = { enumerable: true, get: function() { return m[k]; } };
16
- }
17
- Object.defineProperty(o, k2, desc);
18
- }) : (function(o, m, k, k2) {
19
- if (k2 === undefined) k2 = k;
20
- o[k2] = m[k];
21
- }));
22
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
- Object.defineProperty(o, "default", { enumerable: true, value: v });
24
- }) : function(o, v) {
25
- o["default"] = v;
26
- });
27
- var __importStar = (this && this.__importStar) || (function () {
28
- var ownKeys = function(o) {
29
- ownKeys = Object.getOwnPropertyNames || function (o) {
30
- var ar = [];
31
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
- return ar;
33
- };
34
- return ownKeys(o);
35
- };
36
- return function (mod) {
37
- if (mod && mod.__esModule) return mod;
38
- var result = {};
39
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
- __setModuleDefault(result, mod);
41
- return result;
42
- };
43
- })();
44
11
  Object.defineProperty(exports, "__esModule", { value: true });
45
12
  // Use Commander for initial parsing
46
13
  const commander_1 = require("commander");
47
14
  // Import the server setup function
48
15
  const server_js_1 = require("./server.js"); // Adjust path if needed
49
- const path = __importStar(require("path")); // Import path module
50
16
  console.error(">>> BIN.TS Starting <<<");
51
17
  // Define version from package.json
52
18
  // Use import assertion for JSON in ES modules if possible, otherwise require is okay for CommonJS
@@ -69,12 +35,9 @@ commander_1.program
69
35
  .option('-f, --codesys-profile <profile>', // Changed alias to 'f' to avoid conflict if you add other '-p' options
70
36
  'CODESYS profile name (overrides CODESYS_PROFILE env var)', process.env.CODESYS_PROFILE || 'CODESYS V3.5 SP21' // Default AFTER reading env var
71
37
  )
38
+ // Add workspace option if needed, defaults to cwd
72
39
  .option('-w, --workspace <dir>', 'Workspace directory for relative project paths', process.cwd() // Default to current working directory
73
40
  )
74
- // Add option for the CODESYS scripts directory
75
- .option('-s, --codesys-scripts-dir <dir>', 'Directory containing CODESYS Python scripts (e.g., listener_script.py)',
76
- // Default to a 'codesys_scripts' directory relative to the bin.ts file
77
- path.join(__dirname, '..', 'codesys_scripts'))
78
41
  .parse(process.argv); // Parse the arguments
79
42
  const options = commander_1.program.opts();
80
43
  // --- Log the options Commander parsed ---
@@ -84,7 +47,6 @@ console.error(`BIN.TS Options: ${JSON.stringify(options)}`);
84
47
  console.error(` CODESYS Path Used: ${options.codesysPath}`);
85
48
  console.error(` CODESYS Profile Used: ${options.codesysProfile}`);
86
49
  console.error(` Workspace Used: ${options.workspace}`);
87
- console.error(` CODESYS Scripts Dir Used: ${options.codesysScriptsDir}`); // Log the new option
88
50
  console.error(` Node.js: ${process.version}`);
89
51
  console.error(`========================================`);
90
52
  // --- End Logging ---
@@ -92,8 +54,7 @@ console.error(`========================================`);
92
54
  const serverConfig = {
93
55
  codesysPath: options.codesysPath.trim(),
94
56
  profileName: options.codesysProfile.trim(),
95
- workspaceDir: options.workspace.trim(), // Pass workspace dir
96
- codesysScriptsDir: options.codesysScriptsDir.trim() // Pass the scripts directory
57
+ workspaceDir: options.workspace.trim() // Pass workspace dir
97
58
  };
98
59
  // --- End Prepare Config ---
99
60
  // --- Call the server setup function ---
@@ -60,7 +60,24 @@ const fs = __importStar(require("fs"));
60
60
  const path = __importStar(require("path"));
61
61
  const util_1 = require("util");
62
62
  const readFileAsync = (0, util_1.promisify)(fs.readFile);
63
- const SCRIPTS_DIR = path.join(__dirname, 'scripts');
63
+ // Try multiple possible script locations:
64
+ // 1. Next to the JS files in dist/scripts (when running from node_modules)
65
+ // 2. In the src/scripts directory (when running from source)
66
+ function findScriptsDir() {
67
+ const possiblePaths = [
68
+ path.join(__dirname, 'scripts'), // dist/scripts
69
+ path.join(__dirname, '..', 'src', 'scripts'), // src/scripts from dist
70
+ path.join(__dirname, '..', '..', 'src', 'scripts') // src/scripts from node_modules
71
+ ];
72
+ for (const dir of possiblePaths) {
73
+ if (fs.existsSync(dir)) {
74
+ return dir;
75
+ }
76
+ }
77
+ // Default to the first path and let it fail with a clear error if needed
78
+ return possiblePaths[0];
79
+ }
80
+ const SCRIPTS_DIR = findScriptsDir();
64
81
  // Cache for loaded scripts to avoid repeated file system access
65
82
  const scriptCache = {};
66
83
  /**
@@ -0,0 +1,21 @@
1
+ import sys, scriptengine as script_engine, os, traceback
2
+ project_open = False; project_name = "No project open"; project_path = "N/A"; scripting_ok = False
3
+ try:
4
+ scripting_ok = True; primary_project = script_engine.projects.primary
5
+ if primary_project:
6
+ project_open = True
7
+ try:
8
+ project_path = os.path.normcase(os.path.abspath(primary_project.path))
9
+ try:
10
+ project_name = primary_project.get_name() # Might fail
11
+ if not project_name: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
12
+ except: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
13
+ except Exception as e_path: project_path = "N/A (Error: %s)" % e_path; project_name = "Unnamed (Path Error)"
14
+ print("Project Open: %s" % project_open); print("Project Name: %s" % project_name)
15
+ print("Project Path: %s" % project_path); print("Scripting OK: %s" % scripting_ok)
16
+ print("SCRIPT_SUCCESS: Status check complete."); sys.exit(0)
17
+ except Exception as e:
18
+ error_message = "Error during status check: %s" % e
19
+ print(error_message); print("Scripting OK: False")
20
+ # traceback.print_exc() # Optional traceback
21
+ print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
@@ -0,0 +1,65 @@
1
+ import sys, scriptengine as script_engine, os, traceback
2
+
3
+ # Import ensure_project_open function from the template
4
+ # This will be prepended by the script loader
5
+ def ensure_project_open(target_project_path):
6
+ # Function implementation will be included from ensure_project_open.py
7
+ pass
8
+
9
+ try:
10
+ print("DEBUG: compile_project script: Project='%s'" % PROJECT_FILE_PATH)
11
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
12
+ project_name = os.path.basename(PROJECT_FILE_PATH)
13
+ target_app = None
14
+ app_name = "N/A"
15
+
16
+ # Try getting active application first
17
+ try:
18
+ target_app = primary_project.active_application
19
+ if target_app:
20
+ app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (Active)")()
21
+ print("DEBUG: Found active application: %s" % app_name)
22
+ except Exception as active_err:
23
+ print("WARN: Could not get active application: %s. Searching..." % active_err)
24
+
25
+ # If no active app, search for the first one
26
+ if not target_app:
27
+ print("DEBUG: Searching for first compilable application...")
28
+ apps = []
29
+ try:
30
+ # Search recursively through all project objects
31
+ all_children = primary_project.get_children(True)
32
+ for child in all_children:
33
+ # Check using the marker property and if build method exists
34
+ if hasattr(child, 'is_application') and child.is_application and hasattr(child, 'build'):
35
+ app_name_found = getattr(child, 'get_name', lambda: "Unnamed App")()
36
+ print("DEBUG: Found potential application object: %s" % app_name_found)
37
+ apps.append(child)
38
+ break # Take the first one found
39
+ except Exception as find_err: print("WARN: Error finding application object: %s" % find_err)
40
+
41
+ if not apps: raise RuntimeError("No compilable application found in project '%s'" % project_name)
42
+ target_app = apps[0]
43
+ app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (First Found)")()
44
+ print("WARN: Compiling first found application: %s" % app_name)
45
+
46
+ print("DEBUG: Calling build() on app '%s'..." % app_name)
47
+ if not hasattr(target_app, 'build'):
48
+ raise TypeError("Selected object '%s' is not an application or doesn't support build()." % app_name)
49
+
50
+ # Execute the build
51
+ target_app.build();
52
+ print("DEBUG: Build command executed for application '%s'." % app_name)
53
+
54
+ # Check messages is harder without direct access to message store from script.
55
+ # Rely on CODESYS UI or log output for now.
56
+ print("Compile Initiated For Application: %s" % app_name)
57
+ print("In Project: %s" % project_name)
58
+ print("SCRIPT_SUCCESS: Application compilation initiated.")
59
+ sys.exit(0)
60
+ except Exception as e:
61
+ detailed_error = traceback.format_exc()
62
+ error_message = "Error initiating compilation for project %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
63
+ print(error_message)
64
+ print("SCRIPT_ERROR: %s" % error_message)
65
+ sys.exit(1)
@@ -0,0 +1,80 @@
1
+ import sys, scriptengine as script_engine, os, traceback
2
+
3
+ # Import ensure_project_open function from the template
4
+ # This will be prepended by the script loader
5
+ def ensure_project_open(target_project_path):
6
+ # Function implementation will be included from ensure_project_open.py
7
+ pass
8
+
9
+ # Import find_object_by_path function from the template
10
+ # This will be prepended by the script loader
11
+ def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
12
+ # Function implementation will be included from find_object_by_path.py
13
+ pass
14
+
15
+ PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
16
+ METHOD_NAME = "{METHOD_NAME}"
17
+ RETURN_TYPE = "{RETURN_TYPE}" # Can be empty string for no return type
18
+ # Optional: Language
19
+ # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
20
+
21
+ try:
22
+ print("DEBUG: create_method script: ParentPOU='%s', Name='%s', ReturnType='%s', Project='%s'" % (PARENT_POU_FULL_PATH, METHOD_NAME, RETURN_TYPE, PROJECT_FILE_PATH))
23
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
24
+ if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
25
+ if not METHOD_NAME: raise ValueError("Method name empty.")
26
+ # RETURN_TYPE can be empty
27
+
28
+ # Find the parent POU object
29
+ parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
30
+ if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
31
+
32
+ parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
33
+ print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
34
+
35
+ # Check if parent object supports creating methods (should implement ScriptIecLanguageMemberContainer)
36
+ if not hasattr(parent_pou_object, 'create_method'):
37
+ raise TypeError("Parent object '%s' of type %s does not support create_method." % (parent_pou_name, type(parent_pou_object).__name__))
38
+
39
+ # Default language to None (usually ST)
40
+ lang_guid = None
41
+ # Use None if RETURN_TYPE is empty string, otherwise use the string
42
+ actual_return_type = RETURN_TYPE if RETURN_TYPE else None
43
+ print("DEBUG: Calling create_method: Name='%s', ReturnType=%s, Lang=%s" % (METHOD_NAME, actual_return_type, lang_guid))
44
+
45
+ # Call the create_method method ON THE PARENT POU
46
+ new_method_object = parent_pou_object.create_method(
47
+ name=METHOD_NAME,
48
+ return_type=actual_return_type,
49
+ language=lang_guid # Pass None to use default
50
+ )
51
+
52
+ if new_method_object:
53
+ new_meth_name = getattr(new_method_object, 'get_name', lambda: METHOD_NAME)()
54
+ print("DEBUG: Method object created: %s" % new_meth_name)
55
+
56
+ # --- SAVE THE PROJECT TO PERSIST THE NEW METHOD OBJECT ---
57
+ try:
58
+ print("DEBUG: Saving Project (after method creation)...")
59
+ primary_project.save()
60
+ print("DEBUG: Project saved successfully after method creation.")
61
+ except Exception as save_err:
62
+ print("ERROR: Failed to save Project after creating method: %s" % save_err)
63
+ detailed_error = traceback.format_exc()
64
+ error_message = "Error saving Project after creating method '%s': %s\\n%s" % (METHOD_NAME, save_err, detailed_error)
65
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
66
+ # --- END SAVING ---
67
+
68
+ print("Method Created: %s" % new_meth_name)
69
+ print("Parent POU: %s" % PARENT_POU_FULL_PATH)
70
+ print("Return Type: %s" % (RETURN_TYPE if RETURN_TYPE else "(None)"))
71
+ print("SCRIPT_SUCCESS: Method created successfully.")
72
+ sys.exit(0)
73
+ else:
74
+ error_message = "Failed to create method '%s' under '%s'. create_method returned None." % (METHOD_NAME, parent_pou_name)
75
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
76
+
77
+ except Exception as e:
78
+ detailed_error = traceback.format_exc()
79
+ error_message = "Error creating method '%s' under POU '%s' in project '%s': %s\\n%s" % (METHOD_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
80
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
@@ -0,0 +1,96 @@
1
+ import sys, scriptengine as script_engine, os, traceback
2
+
3
+ # Import ensure_project_open function from the template
4
+ # This will be prepended by the script loader
5
+ def ensure_project_open(target_project_path):
6
+ # Function implementation will be included from ensure_project_open.py
7
+ pass
8
+
9
+ # Import find_object_by_path function from the template
10
+ # This will be prepended by the script loader
11
+ def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
12
+ # Function implementation will be included from find_object_by_path.py
13
+ pass
14
+
15
+ POU_NAME = "{POU_NAME}"
16
+ POU_TYPE_STR = "{POU_TYPE_STR}"
17
+ IMPL_LANGUAGE_STR = "{IMPL_LANGUAGE_STR}"
18
+ PARENT_PATH_REL = "{PARENT_PATH}"
19
+
20
+ pou_type_map = {
21
+ "Program": script_engine.PouType.Program,
22
+ "FunctionBlock": script_engine.PouType.FunctionBlock,
23
+ "Function": script_engine.PouType.Function
24
+ }
25
+ # Map common language names to ImplementationLanguages attributes if needed (optional, None usually works)
26
+ # lang_map = { "ST": script_engine.ImplementationLanguage.st, ... }
27
+
28
+ try:
29
+ print("DEBUG: create_pou script: Name='%s', Type='%s', Lang='%s', ParentPath='%s', Project='%s'" % (POU_NAME, POU_TYPE_STR, IMPL_LANGUAGE_STR, PARENT_PATH_REL, PROJECT_FILE_PATH))
30
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
31
+ if not POU_NAME: raise ValueError("POU name empty.")
32
+ if not PARENT_PATH_REL: raise ValueError("Parent path empty.")
33
+
34
+ # Resolve POU Type Enum
35
+ pou_type_enum = pou_type_map.get(POU_TYPE_STR)
36
+ if not pou_type_enum: raise ValueError("Invalid POU type string: %s. Use Program, FunctionBlock, or Function." % POU_TYPE_STR)
37
+
38
+ # Find parent object using the robust function
39
+ parent_object = find_object_by_path_robust(primary_project, PARENT_PATH_REL, "parent container")
40
+ if not parent_object: raise ValueError("Parent object not found for path: %s" % PARENT_PATH_REL)
41
+
42
+ parent_name = getattr(parent_object, 'get_name', lambda: str(parent_object))()
43
+ print("DEBUG: Using parent object: %s (Type: %s)" % (parent_name, type(parent_object).__name__))
44
+
45
+ # Check if parent object supports creating POUs (should implement ScriptIecLanguageObjectContainer)
46
+ if not hasattr(parent_object, 'create_pou'):
47
+ raise TypeError("Parent object '%s' of type %s does not support create_pou." % (parent_name, type(parent_object).__name__))
48
+
49
+ # Set language GUID to None (let CODESYS default based on parent/settings)
50
+ lang_guid = None
51
+ print("DEBUG: Setting language to None (will use default).")
52
+ # Example if mapping language string: lang_guid = lang_map.get(IMPL_LANGUAGE_STR, None)
53
+
54
+ print("DEBUG: Calling parent_object.create_pou: Name='%s', Type=%s, Lang=%s" % (POU_NAME, pou_type_enum, lang_guid))
55
+
56
+ # Call create_pou using keyword arguments
57
+ new_pou = parent_object.create_pou(
58
+ name=POU_NAME,
59
+ type=pou_type_enum,
60
+ language=lang_guid # Pass None
61
+ )
62
+
63
+ print("DEBUG: parent_object.create_pou returned: %s" % new_pou)
64
+ if new_pou:
65
+ new_pou_name = getattr(new_pou, 'get_name', lambda: POU_NAME)()
66
+ print("DEBUG: POU object created: %s" % new_pou_name)
67
+
68
+ # --- SAVE THE PROJECT TO PERSIST THE NEW POU ---
69
+ try:
70
+ print("DEBUG: Saving Project...")
71
+ primary_project.save() # Save the overall project file
72
+ print("DEBUG: Project saved successfully after POU creation.")
73
+ except Exception as save_err:
74
+ print("ERROR: Failed to save Project after POU creation: %s" % save_err)
75
+ detailed_error = traceback.format_exc()
76
+ error_message = "Error saving Project after creating POU '%s': %s\\n%s" % (new_pou_name, save_err, detailed_error)
77
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
78
+ # --- END SAVING ---
79
+
80
+ print("POU Created: %s" % new_pou_name)
81
+ print("Type: %s" % POU_TYPE_STR)
82
+ print("Language: %s (Defaulted)" % IMPL_LANGUAGE_STR)
83
+ print("Parent Path: %s" % PARENT_PATH_REL)
84
+ print("SCRIPT_SUCCESS: POU created successfully.")
85
+ sys.exit(0)
86
+ else:
87
+ error_message = "Failed to create POU '%s'. create_pou returned None." % POU_NAME
88
+ print(error_message)
89
+ print("SCRIPT_ERROR: %s" % error_message)
90
+ sys.exit(1)
91
+ except Exception as e:
92
+ detailed_error = traceback.format_exc()
93
+ error_message = "Error creating POU '%s' in project '%s': %s\\n%s" % (POU_NAME, PROJECT_FILE_PATH, e, detailed_error)
94
+ print(error_message)
95
+ print("SCRIPT_ERROR: Error creating POU '%s': %s" % (POU_NAME, e))
96
+ sys.exit(1)
@@ -0,0 +1,54 @@
1
+ import sys, scriptengine as script_engine, os, shutil, time, traceback
2
+ # Placeholders
3
+ TEMPLATE_PROJECT_PATH = r'{TEMPLATE_PROJECT_PATH}' # Path to Standard.project
4
+ PROJECT_FILE_PATH = r'{PROJECT_FILE_PATH}' # Path for the new project (Target Path)
5
+ try:
6
+ print("DEBUG: Python script create_project (copy from template):")
7
+ print("DEBUG: Template Source = %s" % TEMPLATE_PROJECT_PATH)
8
+ print("DEBUG: Target Path = %s" % PROJECT_FILE_PATH)
9
+ if not PROJECT_FILE_PATH: raise ValueError("Target project file path empty.")
10
+ if not TEMPLATE_PROJECT_PATH: raise ValueError("Template project file path empty.")
11
+ if not os.path.exists(TEMPLATE_PROJECT_PATH): raise IOError("Template project file not found: %s" % TEMPLATE_PROJECT_PATH)
12
+
13
+ # 1. Copy the template project file to the new location
14
+ target_dir = os.path.dirname(PROJECT_FILE_PATH)
15
+ if not os.path.exists(target_dir): print("DEBUG: Creating target directory: %s" % target_dir); os.makedirs(target_dir)
16
+ # Check if target file already exists
17
+ if os.path.exists(PROJECT_FILE_PATH): print("WARN: Target project file already exists, overwriting: %s" % PROJECT_FILE_PATH)
18
+
19
+ print("DEBUG: Copying '%s' to '%s'..." % (TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH))
20
+ shutil.copy2(TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH) # copy2 preserves metadata
21
+ print("DEBUG: File copy complete.")
22
+
23
+ # 2. Open the newly copied project file
24
+ print("DEBUG: Opening the copied project: %s" % PROJECT_FILE_PATH)
25
+ # Set flags for silent opening
26
+ update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
27
+ # try:
28
+ # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
29
+ # except AttributeError:
30
+ # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
31
+ # update_mode = 3
32
+
33
+ project = script_engine.projects.open(PROJECT_FILE_PATH, update_flags=update_mode)
34
+ print("DEBUG: script_engine.projects.open returned: %s" % project)
35
+ if project:
36
+ print("DEBUG: Pausing briefly after open...")
37
+ time.sleep(1.0) # Allow CODESYS to potentially initialize things
38
+ try:
39
+ print("DEBUG: Explicitly saving project after opening copy...")
40
+ project.save();
41
+ print("DEBUG: Project save after opening copy succeeded.")
42
+ except Exception as save_err:
43
+ print("WARN: Explicit save after opening copy failed: %s" % save_err)
44
+ # Decide if this is critical - maybe not, but good to know.
45
+ print("Project Created from Template Copy at: %s" % PROJECT_FILE_PATH)
46
+ print("SCRIPT_SUCCESS: Project copied from template and opened successfully.")
47
+ sys.exit(0)
48
+ else:
49
+ error_message = "Failed to open project copy %s after copying template %s. projects.open returned None." % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH)
50
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
51
+ except Exception as e:
52
+ detailed_error = traceback.format_exc()
53
+ error_message = "Error creating project '%s' from template '%s': %s\\n%s" % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH, e, detailed_error)
54
+ print(error_message); print("SCRIPT_ERROR: Error copying/opening template: %s" % e); sys.exit(1)
@@ -0,0 +1,78 @@
1
+ import sys, scriptengine as script_engine, os, traceback
2
+
3
+ # Import ensure_project_open function from the template
4
+ # This will be prepended by the script loader
5
+ def ensure_project_open(target_project_path):
6
+ # Function implementation will be included from ensure_project_open.py
7
+ pass
8
+
9
+ # Import find_object_by_path function from the template
10
+ # This will be prepended by the script loader
11
+ def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
12
+ # Function implementation will be included from find_object_by_path.py
13
+ pass
14
+
15
+ PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
16
+ PROPERTY_NAME = "{PROPERTY_NAME}"
17
+ PROPERTY_TYPE = "{PROPERTY_TYPE}"
18
+ # Optional: Language for Getter/Setter (usually defaults to ST)
19
+ # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
20
+
21
+ try:
22
+ print("DEBUG: create_property script: ParentPOU='%s', Name='%s', Type='%s', Project='%s'" % (PARENT_POU_FULL_PATH, PROPERTY_NAME, PROPERTY_TYPE, PROJECT_FILE_PATH))
23
+ primary_project = ensure_project_open(PROJECT_FILE_PATH)
24
+ if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
25
+ if not PROPERTY_NAME: raise ValueError("Property name empty.")
26
+ if not PROPERTY_TYPE: raise ValueError("Property type empty.")
27
+
28
+ # Find the parent POU object
29
+ parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
30
+ if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
31
+
32
+ parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
33
+ print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
34
+
35
+ # Check if parent object supports creating properties (should implement ScriptIecLanguageMemberContainer)
36
+ if not hasattr(parent_pou_object, 'create_property'):
37
+ raise TypeError("Parent object '%s' of type %s does not support create_property." % (parent_pou_name, type(parent_pou_object).__name__))
38
+
39
+ # Default language to None (usually ST)
40
+ lang_guid = None
41
+ print("DEBUG: Calling create_property: Name='%s', Type='%s', Lang=%s" % (PROPERTY_NAME, PROPERTY_TYPE, lang_guid))
42
+
43
+ # Call the create_property method ON THE PARENT POU
44
+ new_property_object = parent_pou_object.create_property(
45
+ name=PROPERTY_NAME,
46
+ return_type=PROPERTY_TYPE,
47
+ language=lang_guid # Pass None to use default
48
+ )
49
+
50
+ if new_property_object:
51
+ new_prop_name = getattr(new_property_object, 'get_name', lambda: PROPERTY_NAME)()
52
+ print("DEBUG: Property object created: %s" % new_prop_name)
53
+
54
+ # --- SAVE THE PROJECT TO PERSIST THE NEW PROPERTY OBJECT ---
55
+ try:
56
+ print("DEBUG: Saving Project (after property creation)...")
57
+ primary_project.save()
58
+ print("DEBUG: Project saved successfully after property creation.")
59
+ except Exception as save_err:
60
+ print("ERROR: Failed to save Project after creating property: %s" % save_err)
61
+ detailed_error = traceback.format_exc()
62
+ error_message = "Error saving Project after creating property '%s': %s\\n%s" % (PROPERTY_NAME, save_err, detailed_error)
63
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
64
+ # --- END SAVING ---
65
+
66
+ print("Property Created: %s" % new_prop_name)
67
+ print("Parent POU: %s" % PARENT_POU_FULL_PATH)
68
+ print("Type: %s" % PROPERTY_TYPE)
69
+ print("SCRIPT_SUCCESS: Property created successfully.")
70
+ sys.exit(0)
71
+ else:
72
+ error_message = "Failed to create property '%s' under '%s'. create_property returned None." % (PROPERTY_NAME, parent_pou_name)
73
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
74
+
75
+ except Exception as e:
76
+ detailed_error = traceback.format_exc()
77
+ error_message = "Error creating property '%s' under POU '%s' in project '%s': %s\\n%s" % (PROPERTY_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
78
+ print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)