Skip to content

Import

In short: edit the .st files under project-view/, cts compare, cts import --dry-run, cts import, cts build. The rest is what import does in each case.

cts import applies project-view/ changes to the open IDE project. Its first line is the verdict — ok import text=1 native=0 s=0.1 saved=false — and the objects it carried follow; --json prints the whole payload. The IDE must be offline from the PLC; run cts disconnect first. Import changes memory only unless --save is used.

Import writes through the in-IDE agent. Text objects (.st) go by objects, compared with the sync base refs/cts/base (the view when disk and IDE last agreed): only the parts changed on disk are written (objects.settext, settings from the cts pragma), a new object is created below its parent, an object the IDE changed too is merged with git merge-file (named under merged), and overlapping edits are a conflict named under conflicts (exit 2, each with its key and its disk and ide text; --force or --take disk writes the disk text, --take ide keeps the IDE text and writes it to the file, --take KEY=disk|ide settles one object, KEY its file or its conflict's key, in cts import, cts sync and cts export; with --tool, a person's choice, each opens side by side in the merge program, the disk's text on the right, and what is saved there goes to both). An object only the IDE changed stays, named under ide_changed (ide_changed=N in the line); --force gives it, and every .xml object the IDE changed, the disk's text too, creates again an object of the last sync the IDE deleted, and deletes an object only the IDE has, a text object or one with no text form (deny key delete_pou), after what the IDE held went on refs/cts/overwritten. An object deleted on disk that the IDE changed since the last sync refuses the import (--force deletes it, its IDE text kept on refs/cts/overwritten). An object the import would write, delete or rename that someone edits in the IDE after the import read it (while a merge window is open, say) refuses the run before anything is written: run it again, and the edit is merged like any other. Objects it cannot carry come back in not_importable. To carry the IDE's own changes to disk as well, use cts sync: to correct an object only the IDE changed (ide_changed), run cts sync so its file holds the IDE's text, edit the file, then cts import.

A text list's texts (the TextDefault and language cells of GlobalTextList.csv, each row found by its TextID) take the agent path too, and so do rows added or removed (added at the end of the IDE's list through objects.listadd, removed through objects.listremove); the order of the rows does not matter, the .csv lists them by TextID. The list's .xml has no rows; a changed or new one goes to the IDE with the .csv's rows put back. A text list the IDE changed too is merged by its lines; a row both changed is a conflict named under conflicts like a text object's, its key the .csv (an alarm group both changed refuses the import). An alarm group's messages (the Message columns of its .csv) go the same way, into the rows of its message list whose TextID is the alarm's AlarmID; the other columns are the IDE's to change. An object with no text form (a task, a visualisation, a device, a folder's .cds-object.xml, the project settings) takes the agent path too: its changed .xml is written back as the whole object through objects.replace, with an objects.export of it compared in the normal form as the read-back; a property of its MetaObject the file changed (a visualization's size) travels with it, a changed name does not and makes the run partial. One the IDE changed too is merged element by element; a value both changed is a conflict under conflicts, its key the .xml, --take settles it. The first line carries the elapsed s; a rename of an object (both files) takes the agent path too: a removed and an added object of one kind whose texts are alike (the same but for the names of objects that left or came, or half their lines the same) are one rename, the object and its members keep their GUIDs and get the new text. Deleting all of an object's files deletes the object through the agent (objects.delete, gated by the delete_pou deny key: a project that denies it refuses the whole run, the other changes too, with a cts permissions hint before anything is written). Deleting only some of its files, a folder, a move to another folder, a text object's .xml or another non-.st projection change, or an added file the agent cannot make writes nothing: the import is refused with one line naming the reason, exit 1. Make that change in the IDE, or undo it on disk. --save saves through the agent after a clean read-back and reports saved=true; a partial run never saves. An object whose read-back is not same is partial, the way an unapplied object is.

cts import --dry-run writes nothing: it runs the compare (through the agent) and the same plan the import makes, and answers as an import, not as a compare: ok import planned modified=1 added=0 deleted=0 not_importable=0 — the compare's counts without unchanged, which a plan does not need. When the import would be refused, the next line names the reason (cannot import by objects: changed POUs/f.xml: the settings of a text object), the IDE being online with the PLC among them (the refusal cts import would give, naming cts disconnect).

Where a visualisation's text lives

A visualisation does not own its strings. The control carries two members: the text id it refers to, and the value that id resolved to when the IDE last wrote the file. The string itself is an entry in the global text list, whose editable projection is POUs/GlobalTextList.csv — one <TextID>,<TextDefault> row per string. POUs/GlobalTextList.xml beside it holds the rest of the list (its languages) and no rows.

So changing a button's caption is one edit in one file: the row in GlobalTextList.csv that holds the string, if the control already refers to a text-list entry. cts import carries it, and the control's own XML needs no change at all. A new row in the CSV becomes a new text-list row at that position; a control only shows it once it refers to that TextID, so a new visu string is usually easier to add in the IDE, which picks the id, and export.

Editing the caption in the visu XML works, but only when the text-list row it references agrees with it. Measured live (t76): a visu-XML caption edit whose referenced GlobalTextList row was changed with it (or changed first) verified cleanly, cts compare 0 in the very next command. A caption edit made alone does not: the control's text member then disagrees with the row its Text-ID member points at, CODESYS refuses that pairing and reallocates a new Text ID on import (it repointed the button from 212 to 665, then to 514, 954, 312 across runs), so the IDE's copy no longer matches disk and the import is partial with verification failed: .../Value: '212' != '665'. The disk file keeps the edit; the fix is to set the referenced row too, or to do the whole edit in the CSV. This is a CODESYS behaviour, not a cts defect — the fingerprint verify is what makes it visible.

cts compare
cts import --dry-run
cts import --save
cts read-object --name X --body   # did the edit land?

New objects must be referenced by a task before download. Check the import result for failed or unapplied objects. Never import into a client project you do not own.