From f318d3d47f0f82bd1e8c75fd166b9141a2755b34 Mon Sep 17 00:00:00 2001 From: Geoff Sokoll Date: Fri, 12 Jun 2026 15:38:06 +1000 Subject: [PATCH 1/2] add export lib to files for library projects Library projects keep POUs, DUTs, GVLs and folders directly under the project root rather than under a Device, so the existing Export To Files exports nothing for them. This adds a separate entry script that walks the project root, skips the service manager objects, and writes the same on-disk format without the device and application levels. Importing a library export back is not yet supported. Credit: reimplemented from ipfedor/codescribe commit a90c84a0. Refs #24. --- README.md | 6 +++ config.json | 7 +++ src/script_lib_export_to_files.py | 86 +++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/script_lib_export_to_files.py diff --git a/README.md b/README.md index 9e9a3c2..891f8be 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,12 @@ Visualisations export as `.vis.xml`. Earlier versions wrote `.xml`, If the target folder is locked and cannot be swapped, the staged files are synced into it instead and the export still succeeds. If that also fails, the error dialog reports the staging folder path; the completed export is preserved there, so nothing is lost. +## Export Lib To Files + +Library projects keep their POUs, DUTs, GVLs and folders directly under the project root rather than under a Device, so `Export To Files` (which walks device entrypoints) exports nothing for them. `Export Lib To Files` walks the project root instead and writes the same on-disk format as `Export To Files`, without the device and application folder levels. Service objects (Library Manager, Project Information, Project Settings, and the Task/Symbol/Visualization/Alarm/Recipe manager objects) are skipped. + +Importing a library export back into a project is not yet supported. + ## Project Templates The intention of CODESCRIBE is not to export a complete copy of the project, but to only export the implementation logic of the project, enabling collaboration via git and other source control methods. An empty, but configured, underlying project file should also be committed to the repo to manage any other configuration that CODESYS provides (e.g. project level configuration, device configuration). For example, `Example Project_template_v1.project`: diff --git a/config.json b/config.json index a1d8569..6fdddbb 100644 --- a/config.json +++ b/config.json @@ -6,6 +6,13 @@ "Path": "codescribe\\src\\script_export_to_files.py", "Params": [] }, + { + "Name": "Export Lib To Files", + "Desc": "Export a library project to files on disk.", + "Icon": "codescribe\\icons\\export_to_files.ico", + "Path": "codescribe\\src\\script_lib_export_to_files.py", + "Params": [] + }, { "Name": "Import From Files", "Desc": "Import from files on disk.", diff --git a/src/script_lib_export_to_files.py b/src/script_lib_export_to_files.py new file mode 100644 index 0000000..5977c11 --- /dev/null +++ b/src/script_lib_export_to_files.py @@ -0,0 +1,86 @@ +# REMEMBER: this is python 2.7 +from __future__ import print_function + +import os + +import scriptengine # type: ignore + +from entrypoint import get_src_folder +from import_export import OBJECT_TYPE_TO_EXPORT_FUNCTION, write_native +from object_type import ObjectType, get_object_type +from util import * + +# Service objects that are not source. The manager objects carry GUIDs that are not in +# GUID_TYPE_MAPPING, so without this list the UNKNOWN fallback below would export them +# as native xml noise. +SKIP_NAMES = [ + "Library Manager", + "Project Information", + "Project Settings", + "Task Configuration", + "Symbol Configuration", + "Visualization Manager", + "Alarm Configuration", + "Recipe Manager", +] + + +def export_child(child_obj, parent_obj, parent_folder_path): + # Keep this consistent with export_child in script_export_to_files.py. Entry + # scripts execute at import time, so it cannot be imported from there. + child_obj_type = get_object_type(child_obj) + export_fn = OBJECT_TYPE_TO_EXPORT_FUNCTION.get(child_obj_type) + if export_fn is not None: + export_fn(child_obj, parent_obj, parent_folder_path, export_child) + return + + if child_obj_type == ObjectType.UNKNOWN: + # An unmapped GUID would otherwise be dropped from the export without a trace + # (seen with diagram objects such as an LD nested inside a CFC). Fall back to a + # native export and keep walking the children. + try: + write_native(child_obj, os.path.join(parent_folder_path, child_obj.get_name() + ".xml"), recursive=False) + except Exception as fallback_error: + print( + "Warning: failed to export '" + + child_obj.get_name() + + "' (unmapped type GUID " + + str(child_obj.type) + + "): " + + str(fallback_error) + ) + for c in child_obj.get_children(): + export_child(c, child_obj, parent_folder_path) + + +try: + print_python_version() + assert_project_open() + + src_folder = get_src_folder(scriptengine.projects.primary) + print("Writing to: " + src_folder) + + staging_folder = begin_export_folder(src_folder) + + for child_obj in scriptengine.projects.primary.get_children(): + if child_obj.get_name() in SKIP_NAMES: + continue + + child_obj_type = get_object_type(child_obj) + if child_obj_type != ObjectType.UNKNOWN and child_obj_type not in OBJECT_TYPE_TO_EXPORT_FUNCTION: + continue + + # Passing None as parent_obj is safe at the top level: the export functions + # that use parent_obj.get_name() (METHOD, PROPERTY, ACTION, TRANSITION) only + # apply to children of POUs, and export_pou passes the POU as the parent when + # recursing into them. + export_child(child_obj, None, staging_folder) + + finalize_export_folder(src_folder, staging_folder) +except Exception as e: + print(e) + ui_error_with_traceback("Export Lib To Files failed.") + raise e + +print("Done!") +ui_info("Export Lib To Files complete.\n\nWrote: " + src_folder) From dc611903c5a6d5fb275bbfe0a687b40d6906317e Mon Sep 17 00:00:00 2001 From: Geoff Sokoll Date: Fri, 12 Jun 2026 16:51:19 +1000 Subject: [PATCH 2/2] skip __VisualizationStyle in library export It is auto-created in every project, its GUID is unmapped, and native export of it fails, so the UNKNOWN fallback printed a warning on every run. Found during live validation on CODESYS SP11. --- src/script_lib_export_to_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/script_lib_export_to_files.py b/src/script_lib_export_to_files.py index 5977c11..03ed9b8 100644 --- a/src/script_lib_export_to_files.py +++ b/src/script_lib_export_to_files.py @@ -22,6 +22,7 @@ "Visualization Manager", "Alarm Configuration", "Recipe Manager", + "__VisualizationStyle", # auto-created in every project; native export of it fails ]