Weld program and .weldplan container
On this page
A weld is described in three layers:
- The seam model: how one weld's line and torch angles are written. Weld nodes in a
robot.v4.program.v2document use it for theirgeometrywhenkindisseam. - The
.weldplancontainer: the program plus everything a planner needs to plan it, packed into one file with digests. This is what the weld planner accepts. - The seam worker protocol: the JSON operations that detect seams in a STEP file, search torch angles and build the container.
A weld program says what weld is required, never how a robot gets there. It holds no joint values, no poses the robot must reach and no robot state. Rotation of the torch about its own electrode axis is left free by default, for the planner to use.
The weld-program.v1 schema#
weld_planner/v1/schema/amr-weld-planner-v1.weld-program.v1.schema.json is the original standalone weld program schema. It is now the source of the seam vocabulary, not a format the planner reads:
- program.v2 copies its definitions of
identifier,vec3,unit_vec3,scalar_function,analytic,curve_segment,curve,orientation_limit_axis,seam,weld_joint,groove_face,weave,sourceandworkpiece. Tests keep the copies identical. - Two differences, both deliberate: in program.v2 a seam has no
processblock (travel speed, standoff and weave are fields of the weld node), andweld_jointssits insideworkpiecerather than at the top level. - A
.weldplanwhoseprogram.jsonis aweld-program.v1document is refused. It has no weld nodes, so it would otherwise open and plan nothing.
Conventions#
- Units are in field names:
_m,_mm,_deg,_rad,_mm_s,_l_min. Geometry is in metres, process quantities in millimetres, angles authored by people in degrees. - Workpiece frame only. All seam geometry is in the frame named by
workpiece.frame_id, which must beworkpiecein a program with welds. The program'splacementsays where that frame sits on the cell. - No baked sampling. Anything that varies along a seam is a function of normalised arc length, never a per-sample array. The planner chooses the sampling.
- Identifiers match
^[A-Za-z_][A-Za-z0-9_:.-]*$.
Curves#
Every curve is a list of clamped, optionally rational B-spline segments joined end to end.
| Field | Type | Required | Description |
|---|---|---|---|
segments[] | array | yes | The segments, in order |
closed | boolean | no | The last segment's end meets the first segment's start |
length_m | number, m | no | Cached total length. Derived and not authoritative. |
Each segment:
| Field | Type | Required | Description |
|---|---|---|---|
degree | integer, 1–7 | yes | 1 with two control points is a line; 2 rational is an exact arc or conic |
control_points_xyz_m | array of 3-vectors, m | yes | At least two |
knots_u | array of numbers | yes | Non-decreasing and clamped. Length is control points + degree + 1. |
weights | array of numbers > 0 | no | Omit for a non-rational spline. One per control point. |
continuity_to_next | string | no | C0, G1, C1 or C2. Advisory: C0 marks a corner. |
analytic | object | no | The exact line, circle or arc, for readability. The B-spline wins if they disagree. |
length_m | number, m | no | Cached, derived |
id, source_edge_id | string | no | Identity and the CAD edge it came from |
A line is degree 1, two control points, knots_u [0, 0, 1, 1]. A quarter arc is degree 2, three control points, weights [1, 0.7071, 1], knots_u [0, 0, 0, 1, 1, 1].
Arc length#
The B-spline parameter u is not a distance. Everything along a seam is defined on normalised arc length s in [0, 1] over the whole curve: s = 0.5 is halfway along the weld, whatever the segments look like. Travel speed is mm/s of arc length.
Scalar functions#
A quantity that varies along the seam, such as an angle or a speed, is one of:
kind | Fields |
|---|---|
constant | value |
piecewise_linear | knots: [s, value] pairs, s strictly increasing from 0.0 to 1.0 |
bspline | degree (≥ 1), knots_u, control_values |
samples | s and values (at least two each), optional interpolation: linear (default), cubic or step |
Seams#
A seam is one pass of one weld.
| Field | Type | Required | Description |
|---|---|---|---|
id | identifier | yes | Unique |
reference | object | yes | Where the 0° work-angle direction comes from; see The seam frame |
orientation | object | yes | work_angle_deg, travel_angle_deg, optional spin and limits |
curve | curve or reference | no | The line the electrode tip follows: inline, or the weld_line of the joint in weld_joint_ref |
weld_joint_ref | identifier or null | no | The weld joint this seam is on. Null means hand-authored. |
span | object | no | start_s, end_s: weld only part of the curve. start_s > end_s wraps through 0, on a closed curve only. |
direction | string | no | forward or reverse along the curve |
pass | object | no | index, role (single, root, fill, cap, tack), offset_in_frame_rb_m |
weld_geometry | object | no | ISO 2553 sizing: leg_length_mm, throat_mm, root_gap_mm, bevel_angle_deg, intermittent |
lead | object | no | lead_in_mm, lead_out_mm along the tangent |
tolerance | object | no | position_mm, work_angle_deg, travel_angle_deg, each > 0 |
sampling_hint | object | no | Advisory only: max_chord_deviation_mm, max_segment_mm, min_segment_mm |
origin | object | no | Why this seam is a separate piece: group_id, index_in_group, group_size, split_reason (none, reachability, continuity, manual), boundary_continuity |
label | string | no | Display name |
tolerance.position_mm is the tolerance of the verifier's tracking certificate for this seam. It defaults to 0.5 mm. See The tracking certificate.
The seam frame#
At each s the seam has a right-handed frame:
P(s) point on the seam curve
t(s) unit tangent, in the travel direction
r(s) reference direction, orthogonalised against t
b(s) = t × rreference.mode says where r comes from:
mode | Needs | Reference direction |
|---|---|---|
rail_curve | rail (a curve) | From the seam point toward the matching point on the rail. Points match by normalised arc length, per segment when the two curves have the same number of segments. |
fixed_vector | fixed_direction_xyz | A fixed direction, orthogonalised against the tangent |
rotation_minimizing | seed_direction_xyz | A seed direction carried along the curve without twisting |
reference.semantics records how the reference was made: bisector, member_face (with member_ref), gravity_projected or custom.
Work and travel angles#
r' = R(t, work_angle) · r roll in the plane across the seam
b' = t × r'
u = R(b', −travel_angle) · r' tilt along travelu points from the weld point toward the torch body. The electrode axis is −u. R is a right-handed rotation.
- Work angle > 0 rotates
rtowardb. 0° puts the torch on the reference direction. - Travel angle > 0 is drag (backhand): the torch leans back over the finished weld.
- Spin, the rotation about the electrode axis, is
freeby default.preferredandlockedtake anangle_degfunction and areferencedirection.
Orientation bands#
orientation.limits.work_angle_deg and orientation.limits.travel_angle_deg bound how far a planner may move each angle. A profile outside its band is a different weld, so a consumer must refuse it rather than clamp it.
| Field | Type | Description |
|---|---|---|
min, max | scalar function, deg | Where the angle may be at all, as a function of s |
max_deviation_deg | number ≥ 0, deg | How far the angle may move from the authored profile, either way |
max_deviation_plus_deg, max_deviation_minus_deg | number ≥ 0, deg | The same, split by direction |
max_rate_deg_per_mm | number ≥ 0, deg/mm | The largest rate of change per millimetre of seam arc |
The shipped weld preset gmaw_steel_fillet ("GMAW · mild steel · 6 mm fillet") authors 0° work and 12° travel, with bands of −15° to 15° work and 0° to 20° travel, each at most 0.5°/mm.
Weld joints#
A weld joint is an objective fact from the CAD: where two parts meet. Seams refer to joints by weld_joint_ref. In program.v2 the list is workpiece.weld_joints.
| Field | Type | Required | Description |
|---|---|---|---|
id | identifier | yes | Unique |
type | string | yes | butt, tee, lap, corner, edge, cruciform, plug or unknown |
members[] | array | yes | At least two: solid_id, optional contact face_ids and role (base, branch, unspecified) |
weld_line | curve | yes | The line where the parts meet |
weld_symbol | string | no | ISO 2553, for example fillet or v_groove |
contact | object | no | kind (coincident, gap, overlap, interference), gap_mm, overlap_area_mm2, max_face_deviation_mm |
groove_faces[] | array | no | Per weld-line segment, the two exposed faces: solid_id, face_id, surface_type, outward_normal_xyz |
dihedral_angle_deg | scalar function, deg | no | The angle between the groove faces, through the open side |
bisector_rail | object | no | A rail along the dihedral bisector: curve, offset_m, continuous, approximate, max_deviation_mm, within_tolerance |
accessibility | object | no | Which stretches no torch direction can reach, found by casting rays: blocked_spans, reachable_runs, open_fraction, suggested_start_s and how they were measured |
accessible_sides, confidence, evidence, label | no | Advisory data from detection |
The .weldplan container#
A .weldplan is a zip file that carries one plan request. It is built by one implementation, weldplan/plan_request.py, through the seam worker's build_plan_request operation, so the bytes are reproducible: the same inputs give the same bytes and the same digests.
| Member | Required | Contents |
|---|---|---|
mimetype | yes | First, stored uncompressed: a fixed media type string, so the format can be identified without parsing JSON |
manifest.json | yes | What is inside, with digests; see below |
program.json | yes | The robot.v4.program.v2 document, including its placement |
cell.json | yes | The cell descriptor: joints, axes, limits, frames. Mesh references are removed and meshes_omitted is true; the planner uses its own copy of the robot's meshes. |
tooling.json | when the program names a tool | The fitted torch |
source/<name>.step | when the program has welds | The workpiece geometry, as bytes |
fixtures.json | no | Workcell bodies in the cell's world frame. Absent means the cell's own placeholder stands; an empty list means there are none. |
context/… | no | The planning context OLP compiled: the corrected URDF, the merged planning numbers, the sphere model and the meshes. The planner reads the robot from here rather than from its own disk. |
JSON members are written with sorted keys and 2-space indentation, and every zip entry carries the fixed timestamp 1980-01-01 00:00:00.
Manifest#
| Field | Description |
|---|---|
schema | amr-weld-planner-v1.plan-request.v1 |
request_id, label, created_utc | Request identity |
app | {module: "amr-weld-planner/v1", ui_version} |
program | {path, program_id, schema, node_count, weld_count} |
cell | {path: "cell.json", id} |
planning_context | {root: "context/", files: [...]}, when a context is packed |
source | {kind: "none"}, or {kind: "step", path, filename, sha256, bytes} |
fixtures | {path: "fixtures.json", count} or null |
planner_owns[] | Sentences naming what the request leaves to the planner: positioner joint values, the cell's collision meshes, and the torch orientation within each seam's bands |
contents[] | {path, sha256, bytes} for every member except mimetype and manifest.json |
Checks when packing#
Packing refuses the request, with a sentence per problem, when:
- the program fails its own program.v2 validation
- the program has welds and
workpiece.frame_idis notworkpiece - a weld is still a hand-placed
manual_pose_wp. OLP lowers those to seams before packing. - there is no
placement, or itsanchor_xyz_m,offset_xyz_morrpy_radis not three finite numbers, or it names nocell_id - the placement's
cell_id, or the program'srobot.model_id, differs from the packed cell's id - the cell has no
workframe - the program has welds and no STEP was given
- the program names a
tooland no tooling was given, or pins one whose digest differs
Packing also stamps two pins into program.json. A tool reference gets the SHA-256 of the packed tooling.json. A robot pin of all zeros takes the packed robot description's identity. A robot pin that names a different description is refused: the description changed since the program was written, so review and accept the new robot settings in OLP, then plan again.
Checks when opening#
The planner refuses a .weldplan unless:
- it is a zip file whose
mimetypeentry is the expected one manifest.jsonexists with the expectedschema- every member in
contentsexists and matches its SHA-256 program.jsonisrobot.v4.program.v2- the program's
robot.robot_description_sha256equals the packed cell's - a program
toolhas atooling.jsonwhose SHA-256 matches its pin - every declared planning-context file exists
The planner's answer names the request by the SHA-256 of the whole file. That digest becomes the plan's program_digest and the root of its plan_id.
Seam worker protocol#
The seam worker is python -m seam_worker.workers --stdin, run in weld_planner/v1. It reads one JSON object from stdin and writes one JSON object to stdout, then exits. OLP starts one per request and forwards the operations through POST /api/offline-programming/v1/seam.
cd weld_planner/v1
echo '{"operation": "sample_seam_frames", "curve": {"segments": [{"degree": 1,
"control_points_xyz_m": [[0,0,0],[0.1,0,0]], "knots_u": [0,0,1,1]}]},
"reference": {"mode": "fixed_vector", "fixed_direction_xyz": [0,0,1]}, "samples": 3}' \
| pixi run -e default python -m seam_worker.workers --stdinoperation | Request fields | Response |
|---|---|---|
detect_joints | step_base64; optional topology, rail_offset_m, intersector, seam_options (min_length_m, max_corner_angle_deg) | {topology, joints}: the B-rep topology, and the weld joints annotated with accessibility, plus one default seam per reachable run |
check_torch_fits | step_base64, joint, seam, torch; optional options | {clearance}: how much of the seam the torch barrel can reach |
plan_torch_path | step_base64, joint, seam, torch, default_standoff_mm, search (work_deviation_deg, travel_deviation_deg; optional bin sizes, knot_spacing_mm, sample_step_mm, ray_count, standoff_cost_deg_per_mm); optional direction (forward or reverse), intersector | {direction, plan, rays_cast, samples, warnings}: the work and travel profile that welds the most arc, closest to what was authored |
sample_seam_frames | curve, reference; optional samples, max_chord_m, max_angle_rad | {frames}: the {P, t, r, b} frame at each sample |
build_plan_request | program, planning_context ({files: {path: base64}}), and step_base64 or workpiece_absent: true; optional step_filename, tooling, fixtures, request_id, label, created_utc | {weldplan_base64, bytes, sha256} |
plan_torch_path searches one direction per call. A reverse search answers with the reversed weld in the welder's own terms, and sets direction_changed in the plan.
A failure is written as {"error": {"code": "…", "detail": "…"}}. The worker's own codes are invalid_request (exit status 2) and worker_failed (exit status 1). OLP adds payload_too_large, busy, timeout, canceled, unavailable and invalid_response; see the OLP seam route.