Skip to content

Commit

Permalink
Merge pull request openpnp#1072 from markmaker/feature/advanced-motio…
Browse files Browse the repository at this point in the history
…n-control--4

Feature/Advanced Motion Control - Update 4
  • Loading branch information
markmaker committed Oct 29, 2020
2 parents 4a96553 + 2fff11f commit dfba626
Show file tree
Hide file tree
Showing 9 changed files with 414 additions and 291 deletions.
21 changes: 21 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
This file lists major or notable changes to OpenPnP in chronological order. This is not
a complete change list, only those that may directly interest or affect users.

# 2020-10-29

## Advanced Motion Control

- Simpler all-GUI setup of machine axes, axis transformations and their assignment to Nozzles,
Cameras etc. (no more machine.xml hacking).
- Making features such as Axis Mapping, Backlash Compensation, Visual Homing, Non-Squareness
Compensation etc. available for all types of drivers (formerly just the GcodeDriver).
- Allowing multiple drivers of mixed types.
- Better control of speed factors: properly control the average speed, including
acceleration/deceleration phases. A move at 50% takes exactly twice as long, regardles of
how short or long the move is.
- Simulated Jerk Control to reduce vibrations, prevent slipping of parts on nozzles, etc.
- Experimental: Motion Blending.
- Asynchronous communication between OpenPnP and (multiple) controllers. Decoupled and
paralellized operation.
- Graphical diagnostics for Motion Planning as a basis for fact based machine optimization.
- Issues & Solutions system to automatically solve machine specific setup and migration
issues, enable advanced features, generate firmware-adapted Gcode commands and regular
expressions.

# 2020-06-23

## Actuator API Change (Non-Breaking)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,42 +348,6 @@ public Wizard getConfigurationWizard() {
public void findIssues(List<Issue> issues) {
super.findIssues(issues);

if (getType() == Type.Rotation) {
if (!isWrapAroundRotation()) {
issues.add(new Solutions.Issue(
this,
"Rotation can be optimized by wrapping-around the shorter way. Best combined with Limit ±180°.",
"Enable Wrap Around.",
Severity.Suggestion,
"https://github.com/openpnp/openpnp/wiki/Machine-Axes#controller-settings-rotational-axis") {

@Override
public void setState(Solutions.State state) throws Exception {
if (confirmStateChange(state)) {
setWrapAroundRotation((state == Solutions.State.Solved));
super.setState(state);
}
}
});
}
if (!isLimitRotation()) {
issues.add(new Solutions.Issue(
this,
"Rotation can be optimized by limiting angles to ±180°. Best combined with Wrap Around.",
"Enable Limit ±180°.",
Severity.Suggestion,
"https://github.com/openpnp/openpnp/wiki/Machine-Axes#controller-settings-rotational-axis") {

@Override
public void setState(Solutions.State state) throws Exception {
if (confirmStateChange(state)) {
setLimitRotation((state == Solutions.State.Solved));
super.setState(state);
}
}
});
}
}
if (getLetter().isEmpty()) {
issues.add(new Solutions.PlainIssue(
this,
Expand All @@ -395,11 +359,11 @@ public void setState(Solutions.State state) throws Exception {
else if (getLetter().equals("E")) {
if (!getDriver().isSupportingPreMove()) {
issues.add(new Solutions.PlainIssue(
this,
"Avoid axis letter E, if possible. Use proper rotation axes instead.",
"Check if your controller supports proper axes A B C instead of E.",
Severity.Warning,
"https://github.com/openpnp/openpnp/wiki/Advanced-Motion-Control#migration-from-a-previous-version"));
this,
"Avoid axis letter E, if possible. Use proper rotation axes instead.",
"Check if your controller supports proper axes A B C instead of E.",
Severity.Warning,
"https://github.com/openpnp/openpnp/wiki/Advanced-Motion-Control#migration-from-a-previous-version"));
}
}
final BacklashCompensationMethod oldBacklashCompensationMethod =
Expand All @@ -425,5 +389,51 @@ public void setState(Solutions.State state) throws Exception {
}
});
}
if (Math.abs(getMotionLimit(1)*2 - getMotionLimit(2)) < 0.1) {
// HACK: migration sets the acceleration to twice the feed-rate, that's our "signal" that the user has not yet
// tuned them.
issues.add(new Solutions.PlainIssue(
this,
"Feed-rate, acceleration, jerk etc. can now be set individually per axis.",
"Tune your machine axes for best speed and acceleration.",
Severity.Suggestion,
"https://github.com/openpnp/openpnp/wiki/Machine-Axes#kinematic-settings--rate-limits"));
}
if (getType() == Type.Rotation) {
if (!isWrapAroundRotation()) {
issues.add(new Solutions.Issue(
this,
"Rotation can be optimized by wrapping-around the shorter way. Best combined with Limit ±180°.",
"Enable Wrap Around.",
Severity.Suggestion,
"https://github.com/openpnp/openpnp/wiki/Machine-Axes#controller-settings-rotational-axis") {

@Override
public void setState(Solutions.State state) throws Exception {
if (confirmStateChange(state)) {
setWrapAroundRotation((state == Solutions.State.Solved));
super.setState(state);
}
}
});
}
if (!isLimitRotation()) {
issues.add(new Solutions.Issue(
this,
"Rotation can be optimized by limiting angles to ±180°. Best combined with Wrap Around.",
"Enable Limit ±180°.",
Severity.Suggestion,
"https://github.com/openpnp/openpnp/wiki/Machine-Axes#controller-settings-rotational-axis") {

@Override
public void setState(Solutions.State state) throws Exception {
if (confirmStateChange(state)) {
setLimitRotation((state == Solutions.State.Solved));
super.setState(state);
}
}
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import org.openpnp.spi.base.AbstractSingleTransformedAxis;
import org.openpnp.spi.base.AbstractTransformedAxis;
import org.openpnp.util.NanosecondTime;
import org.openpnp.util.Triplet;
import org.pmw.tinylog.Logger;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
Expand Down Expand Up @@ -582,6 +583,10 @@ public void moveTo(ReferenceHeadMountable hm, MoveToCommand move)
Double feedRate = move.getFeedRatePerMinute();
Double acceleration = move.getAccelerationPerSecond2();
Double jerk = move.getJerkPerSecond3();
AxesLocation segment = move.getLocation0().motionSegmentTo(allAxesLocation);

double driverDistance = movedAxesLocation.getEuclideanMetric(this, (axis) ->
movedAxesLocation.getLengthCoordinate(axis).convertToUnits(getUnits()).getValue() - axis.getDriverCoordinate()).third;

// Start composing the command, will decide later, whether we actually send it.
String command = getCommand(hm, CommandType.MOVE_TO_COMMAND);
Expand All @@ -599,10 +604,6 @@ public void moveTo(ReferenceHeadMountable hm, MoveToCommand move)
command = substituteVariable(command, "Acceleration", acceleration);
command = substituteVariable(command, "Jerk", jerk);

if (this.usingLetterVariables && this.supportingPreMove) {
throw new Exception(getName()+" configuration error: Using Letter Variables and Pre-Move Commands at the same time is not supported.");
}

ReferenceMachine machine = (ReferenceMachine) hm.getHead().getMachine();
// Get a map of the axes of ...
AxesLocation mappedAxes = (this.usingLetterVariables ?
Expand Down Expand Up @@ -638,7 +639,8 @@ public void moveTo(ReferenceHeadMountable hm, MoveToCommand move)
// The move is definitely on.
doesMove = true;
// TODO: discuss whether we should round to axis resolution here.
double coordinate = allAxesLocation.getCoordinate(axis);
double coordinate = allAxesLocation.getLengthCoordinate(axis)
.convertToUnits(getUnits()).getValue();
double previousCoordinate = axis.getDriverCoordinate();
int direction = ((Double)coordinate).compareTo(previousCoordinate);
// Substitute the axis variables.
Expand All @@ -659,6 +661,11 @@ public void moveTo(ReferenceHeadMountable hm, MoveToCommand move)
sendGcode(preMoveCommand);
}
}
// Axis specific jerk limits are needed on TinyG.
double axisDistance = coordinate - previousCoordinate;
double axisJerk = (jerk != null ? jerk : 0)*Math.abs(axisDistance)/driverDistance;
command = substituteVariable(command, variable+"Jerk", axisJerk > 1 ? axisJerk : null);
command = substituteVariable(command, variable+"JerkMupm3", axisJerk > 4.63 ? axisJerk*1e-6*Math.pow(60, 3) : null); // TinyG: Megaunits/min^3
// Store the new driver coordinate on the axis.
axis.setDriverCoordinate(coordinate);
}
Expand All @@ -670,6 +677,8 @@ public void moveTo(ReferenceHeadMountable hm, MoveToCommand move)
command = substituteVariable(command, "BacklashOffset"+variable, null);
command = substituteVariable(command, variable+"Decreasing", null);
command = substituteVariable(command, variable+"Increasing", null);
command = substituteVariable(command, variable+"Jerk", null);
command = substituteVariable(command, variable+"JerkMupm3", null);
}
}
if (doesMove) {
Expand Down Expand Up @@ -920,7 +929,8 @@ public void sendCommand(String command, long timeout) throws Exception {
protected void waitForConfirmation(String command, long timeout, long wantedConfirmations)
throws Exception {
if (getCommand(null, CommandType.COMMAND_CONFIRM_REGEX) == null) {
throw new Exception(getName()+" configuration error: COMMAND_CONFIRM_REGEX missing");
Logger.warn(getName()+" configuration error: COMMAND_CONFIRM_REGEX missing. Not waiting for confirmation.");
return;
}
// Wait until we get the confirmations count we want.
long t1 = System.currentTimeMillis() + ((timeout == -1) ?
Expand Down Expand Up @@ -1382,6 +1392,7 @@ public void detectFirmware(boolean preserveOldValue) throws Exception {
if (!wasConnected) {
connect();
}

try {
sendCommand("M115");
String firmware = receiveSingleResponse("^FIRMWARE.*");
Expand All @@ -1398,11 +1409,16 @@ public void detectFirmware(boolean preserveOldValue) throws Exception {

/**
* @return true if this is a true Gcode speaking driver/controller rather than some other text protocol.
* The heuristic is simply to look for a G90 command.
* In the absence of a detected firmware, the heuristic is simply to look for a G90 command.
*/
public boolean isSpeakingGcode() {
String command = getCommand(null, CommandType.CONNECT_COMMAND);
return command != null && command.contains("G90");
if (getDetectedFirmware() != null) {
return true;
}
else {
String command = getCommand(null, CommandType.CONNECT_COMMAND);
return command != null && command.contains("G90");
}
}

@Override
Expand Down

0 comments on commit dfba626

Please sign in to comment.