Skip to content

Editing and generating sheets

Agents edit the XML files directly; there is deliberately no edit-operation API. The commands below are text helpers in that spirit: they write XML for the agent (or a person) to place, and the file stays the only state. Generated sheets are rebuilt from a script instead of being edited.

copy

cheme copy ID --prefix NEWID [--tag TAG] [--offset DX,DY] [--sheet FILE] [--into PARENT_ID] [--print] duplicates an object (an id or a unique tag) with its parts and every RepresentationGroup that draws it or a part: symbol, nozzles and their nodes, node positions, labels, the equipment data table. Every id inside the copies is renamed deterministically: the object's id becomes NEWID, an id starting with it keeps its suffix (CentrifugalPump1_Drawing1 -> Pump_P4711B_Drawing1), any other id gets the prefix (Nozzle1 -> Pump_P4711B_Nozzle1). References inside the copy follow; references out of it are kept (shapes, a sensing location), except connections (SourceItem, TargetNode, a ConnectorLine's Source, ...), which would join a node twice and are dropped. --tag sets the tag property (TagName, or PipingComponentNumber, ...; with TagNamePrefix, TagNameSequenceNumber, TagNameSuffix when the object has them) and every label text of the copy that showed the old tag. Drawing coordinates move by --offset (sheet mm, Y down); an EquipmentBarLabel data table goes to the nearest free slot of the table band instead (moved with the offset only when there is none, and said so). The copies are inserted as text next to the originals (with --into, at the end of that object's matching Components and its drawing), so the rest of the file stays byte for byte. It prints the new ids, the file:start-end written, and what was not copied -- the pipes and segments at the original's nozzles, an actuating system's valve reference -- with their lines. --print prints the XML and changes no file. Run cheme fmt and cheme validate afterwards.

An instrument copied with another measured variable follows it in its functions: copy Inst_AI3104 --tag FI-4199 gives the sensor FT4199 (and an actuator AV... FV...). Options that adapt the copy in the same command (each repeatable):

option does
--rename OLD=NEW an inner tag (HV-4146=HV-4147): every copied data value equal to it, the label texts that show it, and the ids that spell it (..._HV4146 -> ..._HV4147)
--renumber OLD:NEW a number (4144:4145) in the copied tags, label texts and ids
--set PROP=VALUE a data value on every copied object that has PROP (FluidCode=Liquid nitrogen on a line and its segments), typed as cheme set types it, and the labels showing it
--connect PIPE[.source\|.target]=ITEM:NODE close a copied pipe's open end (its old or new id): the pipe's Item/Node references, the segment end that pointed at the same place, and the ConnectorLine's Source/Target at the node's position
--sense-at ID[:X,Y] an instrument's SensingLocation; with X,Y a new InstrumentationNodePosition in ID's drawing, where the copied measuring line now starts

Ids inside the copy that spell the original's tag follow --tag too (CV_LOX4004 of line LOX-4004 -> CV_LIN4004). The report ends with duplicate tags:: each tag of the copy that an object on this sheet or another sheet already has, with its line. --connect refuses a node another pipe already ends at.

move, rename, set

Edit commands for objects already on a sheet. Each works on the XML tree (never pyDEXPI's editor), leaves the file as it was except for the change, runs cheme fmt afterwards (--no-fmt not), and with --print prints the change as a unified diff and writes nothing. Without --sheet they use the one sheet holding the id or tag. An unknown id, a taken name or a value of the wrong type exits 2 and changes nothing.

  • cheme move ID --offset DX,DY moves every coordinate that draws ID (the drawings of the object and its parts: symbol, node positions, labels, the bends of lines drawn inside its group whose ends both move) and prints each one, old -> new. ConnectorLines attached to a moved node position follow by reference; their bends stay and are listed as stretched, and a straight one that is no longer horizontal or vertical as now slanted. A PolyLine ending on a moved point gets that end moved. An EquipmentBarLabel data table stays in its band. ID may be a drawing element too, to move only it (a label Text).
  • cheme rename OLD NEW [--children] renames the id OLD and every #OLD reference; --children also every id OLD_* (T4101_I1_Drawing, T4101_I1_Pos). A name already taken is refused. A note says when the object keeps data that spelled the old id (its SubTagName). Given a tag instead of an id (cheme rename HV-4146 HV-4147) it sets the tag data (TagNamePrefix/SequenceNumber/Suffix with a TagName; an instrument's category, letters and number, and the numbers of its sensor and actuator) and the labels showing it; --ids also renames the ids that spell the whole tag (V101, not inside FV101) within the retagged objects, their parts and drawings. A tag already held by another piping component or instrument (this sheet or another) is refused. When two objects share a tag (after a copy), cheme rename ID NEWTAG --tag retags the one with the id ID (rename Inst_Y FI-4150 --tag: category, number, sensor FT4150). A valve that shares its tag with its own actuation chain (XV-104: the valve, the actuating system that operates it, the actuating function driving that system and, when tagged alike, the instrument function) is retagged together with it by cheme rename XV-104 XV-105; any other object with the tag still makes it ambiguous. Tags in PositionNumber (safety valves) address objects like the other tag properties.
  • cheme set ID PROP=VALUE... sets data values by their DEXPI name, in the type the attribute has: the type of the value already there, else pyDEXPI's class -- String, Double, Integer, Boolean, an enumeration member (OnHold=OnHold), a physical quantity with its unit (InsulationThickness='50 mm', '16 barg', '26 MetreCubed'). A dotted name reaches into an aggregate (Stroke.Width=0.5 on a ConnectorLine). PROP= removes the value. Every label whose template shows an attribute of the object gets the cached text pyDEXPI's label renderer gives, so cheme.label-stale stays clean (also on a sheet whose Texts have no ids yet); the lines changed are printed. Setting an instrument's ProcessInstrumentationFunctionCategory or ...Number renumbers its sensor and actuator too, and a tag another object already has is noted.

fragment

cheme fragment KIND --id ID [--sheet FILE] ... prints ready-to-paste XML in two parts, CONCEPTUAL and DRAWING, each after an XML comment saying where it goes (plus a SHAPE part when the symbol is not in the catalogue, e.g. GATE_VALVE_SHAPE). Ids derive from --id:

kind options writes
node --dn 80 [--standard Din2448\|Iso6708\|none] [--at X,Y] a PipingNode with all DN properties; with --at, its PipingNodePosition group
pipe --from ITEM:NODE --to ITEM:NODE [--points x,y;x,y] a Pipe and its ConnectorLine between the two node positions, --points the bends
valve --class GlobeValve\|BallValve\|GateValve\|SwingCheckValve\|ButterflyValve --dn --at X,Y [--rotation R] [--tag] the valve with nodes ID_N1 (in), ID_N2 (out), its symbol (scale 0.4) and node positions 2 mm either side
tee --dn [--branch-dn] --at X,Y [--rotation R] [--branch up\|down\|left\|right] a PipeTee with ID_N1 run in, ID_N2 run out, ID_N3 branch, drawn as in the reference (Shape12 dot, positions 0.001 mm apart)
segment --from ITEM:NODE --to ITEM:NODE [--like SEGMENT] [--number S2] [--dn] a PipingNetworkSegment with its ends, data copied from --like, empty Connections/Items, and its drawing group
label --for ID --attribute A [--at X,Y] a label (ValveLabel, EquipmentTagNameLabel, ... by the object's class) showing the attribute
equipment --class C --tag T --at X,Y [--shape S] [--rotation R] [--scale S\|SX,SY] [--label-at X,Y] [--marker NAME:dy[:SY][:m]] --nozzle N1:dx,dy[:DN] ... equipment of DEXPI class C with tag parts, nozzles (ID_N1, node ID_N1_Node; no DN: an instrument connection), symbol (default by class) with marker symbols such as packed beds and trays, nozzle stubs and node positions, tag label
nozzle --on EQUIP --at dx,dy [--dn] [--tag] (needs --sheet) a nozzle for existing equipment, placed from its symbol, pasted into its Nozzles and its drawing
instrument --tag PIC-4101 --at X,Y --sense ITEM[:x,y] [--field] a ProcessInstrumentationFunction (category, letters, number) with its ProcessSignalGeneratingFunction (sensing location ITEM) and MeasuringLineFunction, bubble, labels, node positions and the drawn measuring line
signal --from INST --to VALVE\|INST[,...] [--fail-action FC\|FO\|FL] [--number] [--points] (needs --sheet) a SignalLineFunction drawn dashed; to a valve also an ActuatingFunction and, when the valve has none, an ActuatingSystem with ControlledActuator and OperatedValveReference (and actuator symbol)
offpage --in\|--out --at X,Y [--rotation R] [--dn] [--note TEXT] [--note-at X,Y] a FlowIn/FlowOutPipeOffPageConnector with node, arrow and note
line --number N --dn [--fluid F] [--at X,Y] a PipingNetworkSystem with empty Segments, its drawing and number label
note --text TEXT --at X,Y a Static text at the top of the diagram, owned by no object (no label-literal)
shape NAME [--id] the catalogue Shape to paste into a sheet that lacks it, or which id it has there

--rotation is the flow direction in degrees (0 left to right, 90 down, 270 up; Y down). With --sheet the shape ids come from the sheet's ShapeCatalogue (looked up by shape name, else the reference ids), a pipe's ends from the sheet's node positions (else <node>_Position, the name a fragment gives them), and a label's cached text from the value it shows. Notes (a node that already has a pipe, a DN without a standard enumeration value) go to stderr. Nothing is written. With --sheet, the comments of the drafting kinds name the holder's id and line.

The template catalogue (empty-sheet.xml, shapes.md) has, besides the reference's shapes, compressors, turbine/expander, blower, column with packed-bed and tray markers, gas filter, silencer, air cooler, general and shell-and-tube exchangers, electric heater, ambient vaporizer, flat-bottom tank, plate-fin exchanger and gate valve (chemeascode.domains.piping.authoring.shapes; their SymbolRegistrationNumber is the shape's name until an ISO 10628-2 number is checked against the standard; none is borrowed from a symbol of another name).

run

cheme run [--fmt SHEET] SCRIPT [ARGS...] runs a Python file in cheme-as-code's own interpreter, where pydexpi and chemeascode (with the authoring API) import. A plant repository has no Python project, so neither python3 nor uv run python can import pyDEXPI there, and the template's Bash(cheme:*) permission already covers cheme run. Arguments after SCRIPT go to the script as sys.argv[1:], its exit code is cheme-as-code's (an uncaught exception prints its traceback and exits 1), and the working directory is the repository root, so relative paths mean the same wherever it is started. --fmt SHEET (before SCRIPT, repeatable) formats a sheet after a successful run (after SCRIPT it goes to the script, and cheme-as-code warns). cheme --version prints the interpreter's path on its second line.

Authoring API (chemeascode.domains.piping.authoring)

Generator scripts build sheets with the documented module chemeascode.domains.piping.authoring instead of their own XML helpers:

import sys
from chemeascode.domains.piping.authoring import (
    Sheet,
    Marker,
    Nozzle,
    equipment,
    line,
    valve,
    tee,
    instrument,
    offpage,
    note,
)

sheet = Sheet.load(sys.argv[1])  # reads the shape catalogue, ids, node positions
pump = equipment(
    sheet,
    "CentrifugalPump",
    "P-101",
    shape="CENTRIFUGAL_PUMP_SHAPE",
    at=(160, 150),
    nozzles=[Nozzle("N1", (-7.5, 0), 80), Nozzle("N2", (0, -7.5), 50)],
)
column = equipment(
    sheet,
    "ProcessColumn",
    "C-101",
    shape="COLUMN_SHAPE",
    at=(260, 120),
    scale=(1.0, 1.6),
    markers=[Marker("COLUMN_PACKED_BED_SHAPE", -20, 1.5), Marker("COLUMN_TRAY_SHAPE", 20)],
)
t = tee("T1")
line(
    sheet,
    "80-W-101",
    dn=80,
    fluid="Water",
    route=[
        offpage("in", at=(40, 150), note="from P-01"),
        valve("HV101", "GateValve"),
        t,
        valve("FV101", tag="FV-101"),
        pump["N1"],
    ],
)
line(
    sheet, "25-W-103", dn=25, route=[t.branch, (t.branch.xy[0], 180), offpage("out", at=(120, 180))]
)
instrument(
    sheet,
    "FIC-101",
    at=(130, 130),
    sensing="Line_80W101_S2",
    sensing_at=(135, 150),
    signal_to="FV101",
)
note(sheet, "Trip: LALL-101 stops P-101", at=(60, 180))
sheet.save()  # writes, then cheme fmt
builder builds
Sheet.load(path), sheet.save([path]) load a sheet (refusing ids that clash when adding), write and format it
equipment(sheet, cls, tag, shape=, at=, nozzles=[Nozzle(name, (dx, dy), dn)], scale=, markers=) equipment of a DEXPI class with nozzles, symbol, node positions and tag label; eq["N1"] is a nozzle's port. scale=1.2 or scale=(1.0, 1.6) (ScaleX, ScaleY: a tall column); markers=[Marker("COLUMN_PACKED_BED_SHAPE", -20, 1.5), ("COLUMN_TRAY_SHAPE", 10), "COLUMN_TRAY_SHAPE:20:1:m"] draws packed beds and trays on the symbol at dy from its centre, as fragment equipment --marker NAME:dy[:SY][:m] does. A shape missing from an older sheet's catalogue is copied from the template's
line(sheet, number, dn=, fluid=, route=[start, (x, y)..., valve(...), tee(...), end]) the piping network system, its segments (split at tees), pipes with bends, inline items and the number and DN labels, placed beside the route and never on it, clear of symbols, other lines and texts where there is room
valve(id, cls, at=, tag=), tee(id, at=, branch=) inline items for a route (mid-leg without at); t.branch is the tee's branch port
offpage("in"\|"out", at=, note=) an off-page connector starting or ending a route
instrument(sheet, tag, at=, sensing=, sensing_at=, signal_to=, location=) an instrument function with sensor, measuring line and bubble; signal_to a valve adds the actuating system, actuator and signal line
note(sheet, text, at=), label(sheet, for_id, attribute, at=) free text owned by no object; a label as fragment label makes it

Valves, tees, pipes, segments and labels are built by the same functions as cheme fragment, so the XML is identical. Each builder is atomic: one that raises (an id that exists, a valve off its leg) leaves the sheet as it was, so a corrected retry with the same ids works. cheme init puts a runnable example in scripts/example_draft.py.

regen: generated and hand-maintained sheets

A sheet in plant.yaml may name its generator:

sheets:
  - {id: P-04, file: pid/P-04.xml, generator: {script: scripts/draft_P-04.py, frame: 9aa9b38}}

frame is a file in the repository or a git revision whose copy of the sheet file is the start (the commit with the empty sheet). A sheet with a generator is generated: the script is the source and the XML is not edited by hand. Without one it is hand-maintained: the XML is the source and any script is provenance only.

  • cheme regen [SHEET...] copies the frame to a scratch file, runs cheme run SCRIPT FILE (the script edits FILE in place) and fmt, and writes the sheet. It refuses when the sheet differs from what the generator as committed at HEAD makes (someone edited the XML by hand) and names the plant items that differ; --force overwrites anyway. --detach SHEET removes the generator: (keeping the rest of plant.yaml as written): the sheet becomes hand-maintained. --list prints each sheet's state.
  • cheme validate runs each declared generator in a scratch copy of the repository (so whatever else the script writes never reaches the working tree) and warns cheme.generated-sheet-drift when it no longer reproduces the sheet (generator-failed when it cannot run). This executes the scripts plant.yaml declares, also in the pre-commit hook and CI: on a clone you do not trust, run cheme validate --skip-generators, which leaves this out. validate also gives an info cheme.generator-undeclared for each scripts/draft_<sheet>.py of a sheet without generator:.