> However, it has the disadvantage, that uncommitted subordinations might starve while the motion queue is executed.
What do you mean by "starve"?
Do you mean where there is no non-subordinate moveTo() before the next waitForCompletion()?
I would say we should not allow for this to happen. You should always record the subordinate motion before doing a non-subordinate motion, which is like a "commit" for the combo. IMHO, it only works that way, because we need to know which coordinates/axes are non-subordinate, i.e. those four (X Y Z C) coming from the "commit" moveTo().
In fact, now that I think about it, you should even actively reset the recorded subordinate location in waitForCompletion(), so it isn't executed on the next moveTo() if it wasn't properly committed.
Or worse, so it isn't even inherited to the next machine task if a an exception happened! Otherwise this could lead to completely unexpected and potentially dangerous moves!
Note that org.openpnp.spi.base.AbstractMachine.submit() handler
always calls waitForCompletion(), even in case of exception, so
this is granted.
_Mark
Getting back to this:
> Ok, we'll see how that can be handled. I guess either way
is neither just good nor bad. An option to disable pre-rotation
is likely a valuable way out.
We'll just have to document how it behaves. I propose the following:
"Multiple moveTo(location, MotionOption.Subordinate) must be executed in the order in which they are anticipated to happen"
Then when you record the coordinates do it as follows:
subordinateLocation = newLocation.put(subordinateLocation);
so existing subordinate coordinates overwrite new ones of the
same axis, i.e., the first one recorded wins.
For machines with shared rotation axis (as an example) it means
that it will prerotate to the first moveTo(location,
MotionOption.Subordinate) but not the second, etc.
which makes most sense.
For the JobProcessor optimizer, the need to predict the order of the moveTo()s means you need to know the nozzle sort order of the next Step. This will likely complicate things.
Or you could ignore it for now and instruct users of machines
with shared rotation axes to disable the feature.
_Mark
Hi Jan,
Moved over from the other thread:
General remark: do not call this feature "pre-rotate". This term is already used for "pre-rotate" bottom vision (confuses me each time I read it).
Your new feature is at least in theory completely neutral for all
axes. For instance, AbstractHead.moveToSafeZ(double)
could equally move all Z axes at the same time (e.g. quad nozzle
machine, or dedicated Z axes machine).
> I'm digging deeper into the mud and found some new
issues, I'd like to discuss: at present I've implemented
pre-rotation as separate steps in the job processor located
between optimization and actual execution. For picking the
pre-rotation is therefore executed even before the feed, which
is nice, because under the assumption, that the move from place
to pick or feed is much longer then from feed to pick, this is
efficient. However, for ReferencePushPullFeeder used as drag
feeders, the feed operation resets the additional rotation axis
in line 422 (I've configured my peeler as such, with additive
rotation). This trigger - in my current implementation - a
"subordinated motion queue not empty" warning, which voids the
pre-rotation. Could we move this to the end of the feed
operation or handle the reset asynchronously as this axis is
never used elsewhere and it's safe to reset it at any time?
The scenario you describe should not happen in real world, as
your push pull actuator should either have no rotation
axis mapped:
https://github.com/openpnp/openpnp/wiki/ReferencePushPullFeeder#feeder-actuator
Or an axis that is entirely different than the one of the
nozzle:
https://github.com/openpnp/openpnp/wiki/ReferencePushPullFeeder#peeler-axis
I guess this happens because you just reused your regular nozzle
rotation axis in simulation 🙂. Create a new axis, as you would
for a real peeler, and this resetting code should be irrelevant,
i.e. those two axes should not bother each other. Report back if
my assumption are wrong.
Nevertheless, I still see cases where this "resetting" could happen. Often the "current location" of a HeadMountable is taken and then only certain axes altered (and therefore ultimately moved). This use case should preserve the subordinate coordinates.
See callers of AbstractHeadMountable.getLocation().See callers of Location.derive(Double,
Double, Double, Double) and Location.deriveLengths(Length,
Length, Length, Double). There are other ways to do the
same thing.
See also AbstractHeadMountable.substituteUnchangedCoordinates(Location,
Location) where the old Double.NaN
method is implemented.
The first idea is to discuss what AbstractCoordinateAxis.coordinate
reflects where getLocation() ultimately
gets its coordinates. Is it the current planned coordinate with
or without subordinate motion? I'm quite reluctant to
change this, it is used everywhere!
Should it be handled in AbstractHeadMountable.getLocation() where the ramifications can be better judged? More likely.
But it should actually be implemented in AbstractMotionPlanner
with a new method MotionPlanner.getLocation()
and then merely called from AbstractHeadMountable.getLocation().
Separation of concerns.
These my thoughts so far.
_Mark
To unsubscribe from this group and stop receiving emails from it, send an email to openpnp+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/openpnp/794a1f67-9468-448f-9581-99d2cc76c9d1%40makr.zone.
> Therefore I currently have the draining in executeMotionPlan(). ... IIRC we discussed earlier, that pending subordinations shall be removed if the motion planner queue is executed.
Ah, I misunderstood your earlier use of the term "reset". I thought it is was referring to the G92 command, which should really only affect the axes that are actively reset. I understand what you mean, now.
But is there any reason you moved the "subordinate coordinate
reset" into executeMotionPlan() and
not into waitForCompletion() as I
proposed? That's not the same thing, semantically.
> If that's the case in a limited amount of situations, we could trigger executeMotionPlan() with a different or additional argument.
As far as I understand, that is not the case. See above, instead only reset in waitForCompletion().
> Hm... seems it's getting even more complicated...
Yes but things are clearing up, as we sharpen our understanding
of what exactly is happening when. Like with the careful
distinction between "execute" and "wait for completion" above. And
it is an interesting discussion 😎.
> Initially I thought...
What you describe is still mostly accurate... but with a complication or two... see below.
> If anyone wont's to move, it shall say where. And than that move shall be executed based on the current location. Axis that have changed locations, shall move to the new location...Yes, and just to state the obvious, the HeadMountable
(hm) that is actually moved, gives
us the mapped axes that are affected, like a "bitmask" over all
the machine axes. Those masked axes would override subordinate
axes if they were previously registered (this happens in case of
shared axes). But all the non-masked axes can potentially move in
parallel as subordinate axes.
> You also says, that the current location may reflect the
subordination. I don't understand under which conditions this
could be useful.
Short answer: you are kind of right for now. 😇
Long answer: Sometimes, even when you move a particular hm, you don't care to move all axes. I first thought there are existing examples, but turns out I haven't found them where expected.
For instance, the BlindsFeeder may open a cover using the nozzle
tip. It should not care about the nozzle rotation (assuming
run-out is compensated), and it should not unnecessarily cause
rotation and spoil optimized (subordinate) rotation. But turns
out, it currently has its own "rotation optimization" hard-coded
by rotating the nozzle to the feeder's "pick rotation in tape",
see BlindsFeeder.actuateCover(Nozzle,
boolean, boolean, boolean)'s use of BlindsFeeder.getPickRotationInTape().
Needless to say, this current "optimization" easily becomes
counter-productive when you open all covers at job start (many
unnecessary rotations) and in case the pushing nozzle tip is not
the one actually used to pick (nozzle tips can be configured to
allow pushing or not). Also not sure if all RotationModes are properly supported.
For this example to be valid as candidate for your subordinate
motion optimization, and to solve the mentioned
counter-productiveness, you would actually have to modify the BlindsFeeder to use the rotation from nozzle.getLocation() instead. For the
sake of the argument, let's temporarily assume this to be the
case. I hope it then becomes clear that for this use case of hm.getLocation() it should return the
planned location with the subordinate coordinates applied
on top. Otherwise the optimization gets lost.
A similar use case is ReferenceAutoFeeder.isMoveBeforeFeed() and cousins in other feeder classes. But again, if enabled, the feeder currently performs its own hard-coded rotation optimization, and again it can be counter-productive e.g. in case of shared rotation axes.
There is another prominent case I thought was relevant, but as as
it turns out it is even a counter-example, and it adds complexity
😭... read on.
Take hm.moveToSafeZ() for the most
common example of not caring about other axes. The intent is to
make sure the hm is at least at
safe Z. But you don't care about all the other axes. More
specifically, you want to preserve all other axes of the hm at their planned position to not
generate unnecessary motion. Then you take these coordinates from
hm.getLocation() and substitute only
the coordinates you want to change, in this case only Z.
It is then that previously registered subordinate coordinates become more complex.
Obviously, we do not want to rotate on the up-going Safe Z move,
on large rotations it would kinda "screw off" the part while still
half in its pocket, and it would likely slow down the Z move as
the rotation may take longer than the solo Z move.
But we also do not want to lose the registered subordinate C
coordinate (rotation), it must remain registered and only be
executed on the (long) X/Y move while at Safe Z.
Conversely, in case of multiple Z axes (quad or dedicated Z
nozzles), we absolutely want the subordinate Z axes to move at the
same time, on the up-going Safe Z move of the principal hm. So there is a difference in wanted
behavior between types of axes!
I guess we need an enum AxisSubordination
{ Never, InSafeZone, Always } or similar on the ReferenceControllerAxis. With such a
setting subordination can then be configured by axis and it can be
decided which type is needed. I&S should suggest the obvious
behavior according to axis type.
Then in AbstractMotionPlanner.moveTo(HeadMountable, AxesLocation, double, MotionOption...) when you compose the move out of the subordinate and non-subordinate coordinates, you need to use AxesLocation.isInSafeZone() on both currentLocation and newLocation to determine which axes can be superimposed*. After the move, the axes that were not superimposed must remain in the registered subordinate AxesLocation, only the moved ones (including the non-subordinate ones of the hm) must be removed from it.
With this logic the situation around hm.getLocation() actually becomes simpler (for now). Conclusion:
So in the end it is as said in the "short answer" above: you are
kind of right for now. No need to change getLocation() or anything. but in
exchange you need to add enum
AxisSubordination distinction, or equivalent.
_Mark
P.S.
* Side note: strictly
speaking this is a overcautious in case of a multi-head
machine (not to be confused with multi-nozzle machines).
Strictly speaking you should group the axes by their heads, and
only check isInSafeZone() for the
group to which the subordinate axis belongs. But such a multi-head
machine is not currently supported anyways, and for purposed such
as conveyors etc. they are always in their safe zone.