Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
- Visualisation service objects (visualisation manager and related service GUIDs) are recognised and no longer exported.
- `Export To Files` writes the export into a sibling folder named `<project_name>.codescribe_staging` and only swaps it into place once the export completes. Earlier versions deleted the target folder up front, so a locked folder (an open Explorer window, IDE, git client or antivirus) aborted the export immediately and a mid-export crash left the on-disk copy destroyed. 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, so the completed export is preserved. Syncing also removes files the export did not write, as the swap would have, so a rendering from a previous export is never left beside a newer native xml; any that cannot be removed are named.
- Device-tree sibling devices (Ethernet, Modbus and fieldbus devices that sit next to `Plc Logic` as direct children of the PLC device) are exported to `<device>/devices/<name>.xml`, one file per top-level device. On import, each tracked device is removed and recreated from its exported xml; devices fixed by the device package (for example `Local_IO` and `HMI` on IFM hardware) cannot be removed and are skipped with a message, with their configuration carried by the project template. A `_NO_EXPORT` folder added as a direct child of the PLC device disables the device-tree export.
- New `Export Lib To Files` script exports library projects, which keep their objects directly under the project root rather than under a Device. It writes the same on-disk format as `Export To Files`, without the device and application folder levels. Importing a library export back into a project is not yet supported.
- New `Export Lib To Files` script exports library projects, which keep their objects directly under the project root rather than under a Device. It writes the same on-disk format as `Export To Files` in a sibling folder called `POUs`, without the device and application folder levels, so the two exports live side by side without overwriting each other. Service objects (Library Manager, Project Information, Project Settings, and the Task/Symbol/Visualization/Alarm/Recipe manager objects) are skipped.
- New `Import Lib From Files` script reads the `POUs` folder back into a library project. Objects that CODESYS generates implicitly (for example functions generated from the project information) cannot be removed and are left in place during the import instead of aborting it.

## v0.2.0

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ CODESCRIBE supplies five scripts that run inside the CODESYS ScriptEngine and ap
- `Save As Template` generates a "template" project by making a copy of the current project and deleting all exportable objects
- `Update From Template` updates the current working project by copying the template file and importing the plaintext files
- `Export Lib To Files` exports a library project, which keeps its objects directly under the project root rather than under a Device (see [Export Lib To Files](#export-lib-to-files))
- `Import Lib From Files` imports the files exported by `Export Lib To Files` back into a library project

A typical workflow:

Expand Down Expand Up @@ -115,9 +116,9 @@ Exports made with older versions of CODESCRIBE use different filenames for some

## 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.
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` in a sibling folder called `POUs`, 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.
`Import Lib From Files` reads the `POUs` folder back into a library project: it removes tracked objects from the project root and recreates them from the exported files.

## Project Templates

Expand Down Expand Up @@ -209,7 +210,7 @@ Once installed, proceed to [Adding the Script Toolbar to CODESYS](#adding-the-sc

5. Under Categories, scroll down, select ScriptEngine Commands and pick the script you want to add

- Scripts supplied by this repo are `Export To Files`, `Export Lib To Files`, `Import From Files`, `Save As Template` and `Update From Template`
- Scripts supplied by this repo are `Export To Files`, `Export Lib To Files`, `Import From Files`, `Import Lib From Files`, `Save As Template` and `Update From Template`

![Step 5](docs/step_5.png)

Expand Down
7 changes: 7 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
"Path": "codescribe\\src\\script_import_from_files.py",
"Params": []
},
{
"Name": "Import Lib From Files",
"Desc": "Import a library project from files on disk.",
"Icon": "codescribe\\icons\\import_lib_from_files.ico",
"Path": "codescribe\\src\\script_lib_import_from_files.py",
"Params": []
},
{
"Name": "Save As Template",
"Desc": "Save as template.",
Expand Down
Binary file added icons/import_lib_from_files.ico
Binary file not shown.
Binary file added icons/import_lib_from_files.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ def get_src_folder(project):
return src_folder


def get_lib_src_folder(project):
# Sibling of the device/application export folder so the two exports never clash.
working_dir = os.path.dirname(project.path)
project_name, _ = os.path.splitext(os.path.basename(project.path))
return os.path.join(working_dir, "{}.POUs".format(project_name))


def get_device_entrypoints(project):
children = project.get_children()
for child in children:
Expand Down
15 changes: 13 additions & 2 deletions src/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,5 +446,16 @@ def export_visualisation_manager(child_obj, parent_obj, parent_folder_path, expo
def remove_tracked_objects(obj_list):
for obj in obj_list:
if get_object_type(obj) in OBJECT_TYPE_TO_EXPORT_FUNCTION:
print("Removing " + obj.get_name())
obj.remove()
try:
print("Removing " + obj.get_name())
obj.remove()
except StandardError as remove_error:
# Implicitly generated objects (e.g. functions generated from the project
# information) cannot be removed; leave them in place.
print(
"Warning: could not remove '"
+ obj.get_name()
+ "' ("
+ str(remove_error)
+ "); leaving it in place"
)
24 changes: 23 additions & 1 deletion src/import_from_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

from communication_import_export import import_communication
from device_tree_import_export import import_device_tree_siblings
from entrypoint import find_application, find_communication, get_device_entrypoints, get_src_folder
from entrypoint import (
find_application,
find_communication,
get_device_entrypoints,
get_lib_src_folder,
get_src_folder,
)
from import_export import *
from util import *

SKIP_NAMES = ["Library Information"]

def first_word_of_line_iter(f):
for line in f.readlines():
Expand Down Expand Up @@ -97,3 +104,18 @@ def import_from_files(project):
import_communication(communication, device_folder)

import_device_tree_siblings(device_obj, device_folder)


def import_lib_from_files(project):
pous_folder = get_lib_src_folder(project)
print("Reading from: " + pous_folder)
assert_path_exists(pous_folder)

# Service/manager objects are not in OBJECT_TYPE_TO_EXPORT_FUNCTION, so they are
# left in place here (they are ignored by the lib export too).
tracked_children = [obj for obj in project.get_children() if obj.get_name() not in SKIP_NAMES]
remove_tracked_objects(tracked_children)

for child in sorted(os.listdir(pous_folder), key=lambda x: x.count(".")):
if os.path.splitext(child)[0] not in SKIP_NAMES:
import_directory_child(child, pous_folder, project)
5 changes: 3 additions & 2 deletions src/script_lib_export_to_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import scriptengine # type: ignore

from entrypoint import get_src_folder
from entrypoint import get_lib_src_folder
import graphical_export
from import_export import OBJECT_TYPE_TO_EXPORT_FUNCTION, SERVICE_EXPORT_FUNCTIONS, write_native
from object_type import ObjectType, get_object_type
Expand All @@ -17,6 +17,7 @@
SKIP_NAMES = [
"Library Manager",
"Project Information",
"Library Information",
"Project Settings",
"Task Configuration",
"Symbol Configuration",
Expand Down Expand Up @@ -69,7 +70,7 @@ def export_child(child_obj, parent_obj, parent_folder_path):
assert_project_open()
graphical_export.reset_stats()

src_folder = get_src_folder(scriptengine.projects.primary)
src_folder = get_lib_src_folder(scriptengine.projects.primary)
print("Writing to: " + src_folder)

# A device project keeps its objects under Devices, which this walker never
Expand Down
31 changes: 31 additions & 0 deletions src/script_lib_import_from_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# REMEMBER: this is python 2.7
from __future__ import print_function

import scriptengine # type: ignore

from import_from_files import import_lib_from_files
from util import *

try:
print_python_version()
assert_project_open()

ui_continue = scriptengine.system.ui.prompt(
"Import Lib From Files will overwrite unexported changes in the current project!\n\nDo you want to continue?",
choice=scriptengine.PromptChoice.YesNo,
default_result=scriptengine.PromptResult.No,
store_description="Don't show again",
store_key="import_lib_from_files_confirm",
)

if ui_continue == scriptengine.PromptResult.Yes:
import_lib_from_files(scriptengine.projects.primary)
ui_info("Import Lib From Files complete.")
else:
print("Import cancelled.")
except Exception as e:
print(e)
ui_error_with_traceback("Import Lib From Files failed!")
raise e

print("Done!")