A wrist-mounted camera solves the viewing problem on a crowded cell and creates an arithmetic one: axis 6 rotates to reach the parts, the camera's X and Y axes stop lining up with the workobject, and the two numbers you need arrive in a frame that turns with the wrist. The conversion is two chained pose multiplications: ppntnew:=PoseMult(PoseMult(wold.uframe,wold.oframe),ppnt) lifts the point into WOBJ0, then ppntnew:=PoseMult(PoseInv(PoseMult(wnew.uframe,wnew.oframe)),ppntnew) drops it into the new workobject. A routine that runs cleanly but returns [0,0,0] every time fails on a third line, ppntnew:=PoseMult(pa,pb), whose operands were never assigned; deleting that line is the whole fix. This guide rebuilds the routine in ABB RAPID, works the arithmetic on real numbers, and ends with the floor checks that prove the conversion before the robot acts on it.
The chain that converts between workobjects
A wobjdata workobject carries two poses. The uframe says where the fixture sits relative to the robot base; the oframe is a further offset inside it, and in many cells it stays at the identity pose [[0,0,0],[1,0,0,0]] because the fixture offset already lives in the uframe. A robtarget's trans and rot are expressed in that composed frame, so a point means nothing until you name its workobject.
Moving a point between workobjects is a lift and a drop:
PoseMult composes two poses into one, the equivalent of multiplying their 4x4 matrices, and the order matters: PoseMult(a,b) and PoseMult(b,a) give different results as soon as rotations and translations are both in play. PoseInv turns the target's composed frame around, which is what expresses the WOBJ0 point in the new workobject's own axes. Compose frames in one direction and write the result once; that is the same discipline that keeps a waypoint pose relative to a feature from drifting. When the part sits at an angle and the approach direction should follow it, the same two fields carry that rotation, and configuring robot approach direction with workobjects covers that side of the data.
The line that returns [0,0,0] every time
The failing routine declares four pose variables and a jointtarget, every one initialised to [[0,0,0],[1,0,0,0]], then uses two of them. The heart of it is correct:
ppntnew:=PoseMult(PoseMult(wold.uframe,wold.oframe),ppnt);
ppntnew:=PoseMult(PoseInv(PoseMult(wnew.uframe,wnew.oframe)),ppntnew);
The first line lifts the point into WOBJ0, which is why that intermediate step checked out. The second expresses the result in the new workobject. Then comes the destructive line:
ppntnew:=PoseMult(pa,pb);
pa and pb were declared and never assigned, so this multiplies two identity poses, produces the identity pose, and overwrites the finished conversion. The writeback copies that identity into point, and the caller receives [0,0,0] with identity rotation, on every call, for every input. That is the symptom exactly: the intermediate value verifies, the returned value never lands.
A declared pose keeps its initial value until you assign something to it, and PoseMult will compose poses nobody filled without complaint. The jointtarget jP1 and the tooldata arguments told and tnew in the same routine are never read either. Dead declarations are not just noise here; pa and pb are the bug.
The corrected routine, step by step
- Strip the signature to what a workobject conversion uses: the point and the two workobjects.
- Copy the incoming numbers into a pose: ppnt.trans and ppnt.rot.
- Lift to WOBJ0 with the old workobject's composed frame.
- Drop into the new workobject with the inverse of its composed frame.
- Write the result back, and delete the PoseMult(pa,pb) line together with the pb, pa and jP1 declarations.
PROC rCNV_Point(INOUT robtarget point,PERS wobjdata wold,PERS wobjdata wnew)
VAR pose ppnt:=[[0,0,0],[1,0,0,0]];
VAR pose ppntnew:=[[0,0,0],[1,0,0,0]];
!Move information
ppnt.trans:=point.trans;
ppnt.rot:=point.rot;
!Lift to WOBJ0, then drop into the new workobject
ppntnew:=PoseMult(PoseMult(wold.uframe,wold.oframe),ppnt);
ppntnew:=PoseMult(PoseInv(PoseMult(wnew.uframe,wnew.oframe)),ppntnew);
!Return the converted point
point.trans:=ppntnew.trans;
point.rot:=ppntnew.rot;
Two multiplications, one inversion, two poses. Anything more in a frame-conversion routine is dead, and as pa and pb showed, dead code in front of a writeback is destructive.
Worked example: a camera point into a fixture rotated 180 degrees
Inputs, each with its source:
- wold is WOBJ0, the frame the vision side reported the point in: uframe and oframe both [[0,0,0],[1,0,0,0]].
- wnew is the pick fixture: uframe [[600,-200,800],[0,0,1,0]], meaning 600 mm in X, -200 mm in Y and 800 mm in Z from the base, rotated 180 degrees about Z; oframe at identity, the usual cell habit.
- p_old is the camera's feature point, already expressed in WOBJ0: [[210.5,88.0,0.0],[1,0,0,0]].
The rule in words: rotate the point by the inverse of the target frame's rotation, then add the inverse-rotated origin offset. A 180-degree rotation about Z is its own inverse and maps (x, y, z) to (-x, -y, z), so the arithmetic stays in your head:
- wold composes to the identity, so p_wobj0 = (210.5, 88.0, 0.0) mm.
- wnew composes to a 180-degree Z rotation carrying the translation (600, -200, 800) mm.
- Invert it: the rotation stays a 180-degree Z rotation, and the translation becomes -R*t = -(-600, 200, 800) = (600, -200, -800) mm.
- Rotate the point: (210.5, 88.0, 0.0) becomes (-210.5, -88.0, 0.0) mm.
- Add: (-210.5 + 600, -88.0 - 200, 0.0 - 800) = (389.5, -288.0, -800.0) mm.
The feature sits at X 389.5 mm, Y -288.0 mm in the fixture workobject, and those are the two numbers to hand downstream. The Z lands at -800.0 mm and the point's orientation at [0,0,1,0] because the fixture frame's Z axis points down after the flip; harmless unless something uses that orientation as an approach direction. If your copy of the routine returns [0,0,0] on these inputs, the overwrite line is still in it.
Where the axis 6 rotation actually enters
The camera frame rides on the flange, so a wrist rotation turns the measurement axes with it, and that is why raw camera X and Y stop matching the workobject. Where you break the coupling comes down to what the vision side hands you:
- The point arrives already expressed in a shared workobject. The wrist angle at exposure is baked into the numbers, and the two-multiplication chain is the whole job at any axis 6 angle.
- The point arrives in raw camera coordinates. No workobject chain can fix it. Compose the wrist-mount calibration with the robot's position at exposure, the stored jointtarget solved through calcrobT, to reach WOBJ0 first, then apply the chain's second line. The mount numbers come from a wrist-camera calibration: robot mounted camera calibration: capture and solve covers the capture, and eye in hand calibration pose selection covers the robot positions that make it accurate.
For points that are robot positions rather than camera points, calcjointT and calcrobT are the standard pair: joint angles define a pose with no frame attached, so converting to joints and solving back in the new workobject lands the robtarget in it. A fixed camera over the cell sidesteps the moving frame entirely, and eye to hand object pose transform for robot picks shows that route. When every check passes and picks still drift, the next read is diagnosing an eye in hand pick point offset.
Checks that prove the conversion
Run all four before the robot acts on a converted point:
- Identity call. Run the routine with wnew set equal to wold; the output must equal the input to floating-point rounding.
- Round trip. Convert wold to wnew, feed the result back from wnew to wold, and the starting point must return. A persistent offset means one composed frame is not what you think it is.
- Jog test. With wnew active on the pendant, jog the TCP onto the physical feature the camera found. The X and Y shown must match the routine's output; any difference is the hand-eye calibration residual, not this arithmetic, which is exact.
- Zero scan. If every point comes back [0,0,0], walk the routine for a PoseMult whose operands are never assigned. That signature is this bug.
Do the jog test in manual mode at reduced speed with the cell guarded: a wrong workobject sends the robot somewhere nobody planned.
Where the conversion should run
The routine above belongs in the robot program when the camera result is a robtarget the robot consumes directly. When a vision controller hosts the camera instead, compose the frames there and hand the robot a finished point in its active workobject; the robot-side code shrinks to a copy, and the cell keeps one place where frames are composed. The vision controllers category covers that host role: the MV-VC3301H-128G66 runs the camera and the robot link on an Intel Core i3 processor with 5+ LAN ports.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
For a tighter cabinet, the MV-VC2140-128G40-1T is the compact Intel N-series option.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
If the station is still on paper, the configurator starts at the controller step, where this choice sits:
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
When the four checks pass and the numbers still disagree with the part, bring the two workobject definitions and one camera point to the contact page and we will work the chain with you.
Questions
Does the point's quaternion matter when I only read X and Y?
For the translation, no. The output's X and Y depend only on the translation you feed in and on the two composed workobject frames; the quaternion you load into ppnt.rot is carried through the chain and rotated into the new frame, but it never mixes into the position. It matters only when something downstream uses the converted target's orientation, for example as a pick approach direction, and then you should load the camera's measured orientation, not the identity.
Do I need the tooldata parameters in this routine?
No. A conversion between two workobjects is base-frame arithmetic and no tool takes part, which is why the tooldata arguments in the failing routine were never read. Tool data enters one step earlier, while the point is still expressed relative to the wrist: there you compose the camera mount with the robot position at exposure to reach WOBJ0, and only then does the workobject chain apply.
Can calcrobT replace this chain?
For points that are robot positions, yes: calcjointT turns a robtarget into joint angles, which carry no frame at all, and calcrobT solves them back into a robtarget in the new workobject. A camera point was never a robot position, so it has no joint angles to use, and the two-multiplication pose chain is the direct route. Run both routes on one shared point and the answers must match.
Does it matter whether the fixture offset sits in uframe or oframe?
No, provided the composition stays equal. The controller multiplies uframe and oframe into one pose before it places a robtarget, so a fixture offset recorded wholly in the uframe converts exactly like the same geometry split across the two fields. Pick one convention for the whole cell anyway; a person reading the data six months later has to reconstruct the set-up from it.
Why does axis 6 rotation change X and Y at all?
Because the camera frame rides on the flange. A rotation about the flange axis turns the camera's measurement axes with it, so a feature reading X 100 mm, Y 0 in the camera reports X 0, Y 100 mm after a 90-degree turn: the same point, remixed into different axes. The workobject chain cancels that mixing by expressing the point in frames that hold still while the wrist turns.

