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,DYmoves 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 asstretched, and a straight one that is no longer horizontal or vertical asnow slanted. A PolyLine ending on a moved point gets that end moved. AnEquipmentBarLabeldata table stays in its band. ID may be a drawing element too, to move only it (a labelText).cheme rename OLD NEW [--children]renames the id OLD and every#OLDreference;--childrenalso every idOLD_*(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 (itsSubTagName). Given a tag instead of an id (cheme rename HV-4146 HV-4147) it sets the tag data (TagNamePrefix/SequenceNumber/Suffixwith aTagName; an instrument's category, letters and number, and the numbers of its sensor and actuator) and the labels showing it;--idsalso renames the ids that spell the whole tag (V101, not insideFV101) 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 --tagretags the one with the id ID (rename Inst_Y FI-4150 --tag: category, number, sensorFT4150). 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 bycheme rename XV-104 XV-105; any other object with the tag still makes it ambiguous. Tags inPositionNumber(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.5on 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, socheme.label-stalestays clean (also on a sheet whose Texts have no ids yet); the lines changed are printed. Setting an instrument'sProcessInstrumentationFunctionCategoryor...Numberrenumbers 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, runscheme run SCRIPT FILE(the script edits FILE in place) andfmt, 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;--forceoverwrites anyway.--detach SHEETremoves thegenerator:(keeping the rest ofplant.yamlas written): the sheet becomes hand-maintained.--listprints each sheet's state.cheme validateruns each declared generator in a scratch copy of the repository (so whatever else the script writes never reaches the working tree) and warnscheme.generated-sheet-driftwhen it no longer reproduces the sheet (generator-failedwhen it cannot run). This executes the scriptsplant.yamldeclares, also in the pre-commit hook and CI: on a clone you do not trust, runcheme validate --skip-generators, which leaves this out.validatealso gives an infocheme.generator-undeclaredfor eachscripts/draft_<sheet>.pyof a sheet withoutgenerator:.